1 #!/usr/bin/env -S ucode -S 2 3 import * as fs from 'fs'; 4 5 let testdir = sourcepath(0, true); 6 let topdir = fs.realpath(`${testdir}/../..`); 7 8 let line = '........................................'; 9 let ucode_bin = getenv('UCODE_BIN') || `${topdir}/build/ucode`; 10 let ucode_lib = getenv('UCODE_LIB') || `${topdir}/build`; 11 let platform = trim(fs.popen('uname -s', 'r').read('all')); 12 13 function mkdir_p(path) { 14 let parts = split(rtrim(path, '/') || '/', /\/+/); 15 let current = ''; 16 17 for (let part in parts) { 18 current += part + '/'; 19 20 let s = fs.stat(current); 21 22 if (s == null) { 23 if (!fs.mkdir(current)) 24 die(`Error creating directory '${current}': ${fs.error()}`); 25 } 26 else if (s.type != 'directory') { 27 die(`Path '${current}' exists but is not a directory`); 28 } 29 } 30 } 31 32 function shellquote(s) { 33 return `'${replace(s, "'", "'\\''")}'`; 34 } 35 36 function getpid() { 37 return +fs.popen('echo $PPID', 'r').read('all'); 38 } 39 40 function has_expectations(testcase) 41 { 42 return (testcase?.stdout != null || testcase?.stderr != null || testcase?.exitcode != null); 43 } 44 45 function parse_testcases(file, dir) { 46 let fp = fs.open(file, 'r') ?? die(`Unable to open ${file}: ${fs.error()}`); 47 let testcases, testcase, section, m; 48 let code_first = false; 49 50 for (let line = fp.read('line'); length(line); line = fp.read('line')) { 51 if (line == '-- Args --\n') { 52 section = [ 'args', [] ]; 53 } 54 else if (line == '-- Platform --\n') { 55 section = [ 'platform', [] ]; 56 } 57 else if (line == '-- Vars --\n') { 58 section = [ 'env', {} ]; 59 } 60 else if (line == '-- Testcase --\n') { 61 section = [ 'code', '' ]; 62 } 63 else if ((m = match(line, /^-- Expect (stdout|stderr|exitcode) --$/)) != null) { 64 section = [ m[1], '' ]; 65 } 66 else if ((m = match(line, /^-- File (.*)--$/)) != null) { 67 section = [ 'file', `${dir}/files/${trim(m[1]) || 'file'}`, '' ]; 68 } 69 else if ((m = match(line, /^-- End( \(no-eol\))? --$/)) != null) { 70 if (m[1] != null && type(section[-1]) == 'string') 71 section[-1] = substr(section[-1], 0, -1); 72 73 if (section[0] == 'code') { 74 if (testcases == null && !has_expectations(testcase)) 75 code_first = true; 76 77 if (code_first) { 78 if (testcase?.code != null) { 79 push(testcases ??= [], testcase); 80 testcase = null; 81 } 82 83 (testcase ??= {}).code = section[1]; 84 } 85 else { 86 push(testcases ??= [], { ...testcase, code: section[1] }); 87 testcase = null; 88 } 89 } 90 else if (section[0] == 'file') { 91 ((testcase ??= {}).files ??= {})[section[1]] = section[2]; 92 } 93 else { 94 (testcase ??= {})[section[0]] = section[1]; 95 } 96 97 section = null; 98 } 99 else if (section) { 100 switch (section[0]) { 101 case 'args': 102 case 'platform': 103 if ((m = trim(line)) != '') 104 push(section[1], ...split(m, /[ \t\r\n]+/)); 105 break; 106 107 case 'env': 108 if ((m = match(line, /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/)) != null) 109 section[1][m[1]] = m[2]; 110 break; 111 112 default: 113 section[-1] += line; 114 break; 115 } 116 } 117 } 118 119 if (code_first && testcase.code != null && has_expectations(testcase)) 120 push(testcases ??= [], testcase); 121 122 return testcases; 123 } 124 125 function diff(tag, ...ab) { 126 let cmd = [ 'diff', '-au', '--color=always', `--label=Expected ${tag}`, `--label=Resulting ${tag}` ]; 127 let tmpfiles = []; 128 129 for (let i, f in ab) { 130 if (type(f) != 'resource') { 131 push(tmpfiles, fs.mkstemp()); 132 tmpfiles[-1].write(f); 133 f = tmpfiles[-1]; 134 } 135 136 f.seek(0); 137 push(cmd, `/dev/fd/${f.fileno()}`); 138 } 139 140 system(cmd); 141 } 142 143 function run_testcase(num, dir, testcase) { 144 let fout = fs.mkstemp(`${dir}/stdout.XXXXXX`); 145 let ferr = fs.mkstemp(`${dir}/stderr.XXXXXX`); 146 147 let eout = testcase.stdout ?? ''; 148 let eerr = testcase.stderr ?? ''; 149 let ecode = testcase.exitcode ? +testcase.exitcode : null; 150 151 let cmd = join(' ', [ 152 ...map(keys(testcase.env) ?? [], k => `export ${k}=${shellquote(testcase.env[k])};`), 153 `cd ${shellquote(dir)};`, 154 `exec ${ucode_bin}`, 155 `-T','`, 156 `-L ${shellquote(`${ucode_lib}/*.so`)}`, 157 `-D TESTFILES_PATH=${shellquote(`${fs.realpath(dir)}/files`)}`, 158 `-D UCODE_BIN=${shellquote(`${ucode_bin}`)}`, 159 `${join(' ', map(testcase.args ?? [], shellquote))} -`, 160 `>/dev/fd/${fout.fileno()} 2>/dev/fd/${ferr.fileno()}` 161 ]); 162 163 let proc = fs.popen(cmd, 'w') ?? die(`Error launching test command "${cmd}": ${fs.error()}\n`); 164 165 if (testcase.code != null) 166 proc.write(testcase.code); 167 168 let exitcode = proc.close(); 169 170 if (exitcode == 77 && ecode != 77) 171 return null; 172 173 fout.seek(0); 174 ferr.seek(0); 175 176 let ok = true; 177 178 if (replace(ferr.read('all'), dir, '.') != eerr) { 179 if (ok) print('!\n'); 180 printf("Testcase #%d: Expected stderr did not match:\n", num); 181 diff('stderr', eerr, ferr); 182 print("---\n"); 183 ok = false; 184 } 185 186 if (replace(fout.read('all'), dir, '.') != eout) { 187 if (ok) print('!\n'); 188 printf("Testcase #%d: Expected stdout did not match:\n", num); 189 diff('stdout', eout, fout); 190 print("---\n"); 191 ok = false; 192 } 193 194 if (ecode != null && exitcode != ecode) { 195 if (ok) print('!\n'); 196 printf("Testcase #%d: Expected exit code did not match:\n", num); 197 diff('code', `${ecode}\n`, `${exitcode}\n`); 198 print("---\n"); 199 ok = false; 200 } 201 202 return ok; 203 } 204 205 function run_test(file) { 206 let name = fs.basename(file); 207 printf('%s %s ', name, substr(line, length(name))); 208 209 let tmpdir = sprintf('/tmp/test.%d', getpid()); 210 let testcases = parse_testcases(file, tmpdir); 211 let failed = 0, skipped = 0; 212 213 fs.mkdir(tmpdir); 214 215 try { 216 for (let i, testcase in testcases) { 217 if (testcase.platform && index(testcase.platform, platform) < 0) { 218 skipped++; 219 continue; 220 } 221 222 for (let path, data in testcase.files) { 223 mkdir_p(fs.dirname(path)); 224 fs.writefile(path, data) ?? die(`Error writing testcase file "${path}": ${fs.error()}\n`); 225 } 226 227 let ok = run_testcase(i + 1, tmpdir, testcase); 228 229 if (ok == null) 230 skipped++; 231 else 232 failed += !ok; 233 } 234 } 235 catch (e) { 236 warn(`${e.type}: ${e.message}\n${e.stacktrace[0].context}\n`); 237 } 238 239 system(['rm', '-r', tmpdir]); 240 241 if (failed == 0 && skipped > 0) 242 printf('OK (%d/%d skipped)\n', skipped, length(testcases)); 243 else if (failed == 0) 244 print('OK\n'); 245 else 246 printf('%s %s FAILED (%d/%d)\n', name, substr(line, length(name)), failed, length(testcases)); 247 248 return failed; 249 } 250 251 let n_tests = 0; 252 let n_fails = 0; 253 let select_tests = filter(map(ARGV, p => fs.realpath(p)), length); 254 255 function use_test(input) { 256 return fs.access(input = fs.realpath(input)) && 257 (!length(select_tests) || filter(select_tests, p => p == input)[0]); 258 } 259 260 function lib_missing(catdir) { 261 let m = match(fs.basename(catdir), /^([0-9][0-9])_lib_(.+)$/); 262 263 if (m == null) 264 return false; 265 266 for (let ext in ['so', 'dylib', 'dll', 'uc']) 267 if (fs.access(`${ucode_lib}/${m[2]}.${ext}`)) 268 return false; 269 270 return true; 271 } 272 273 for (let catdir in fs.glob(`${testdir}/[0-9][0-9]_*`)) { 274 if (fs.stat(catdir)?.type != 'directory') 275 continue; 276 277 if (lib_missing(catdir)) { 278 printf('\n##\n## Skipping %s tests (no library found)\n##\n\n', substr(fs.basename(catdir), 3)); 279 continue; 280 } 281 282 printf('\n##\n## Running %s tests\n##\n\n', substr(fs.basename(catdir), 3)); 283 284 for (let testfile in fs.glob(`${catdir}/[0-9][0-9]_*`)) { 285 if (!use_test(testfile)) continue; 286 287 n_tests++; 288 n_fails += run_test(testfile); 289 } 290 } 291 292 printf('\nRan %d tests, %d okay, %d failures\n', n_tests, n_tests - n_fails, n_fails); 293 exit(n_fails);
This page was automatically generated by LXR 0.3.1. • OpenWrt