• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/tests/custom/run_tests.uc

  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