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