• 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 
 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