• 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}/ucode`;
 10 let ucode_lib = getenv('UCODE_LIB') || topdir;
 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) --$/s)) != null) {
 60                         section = [ m[1], '' ];
 61                 }
 62                 else if ((m = match(line, /^-- File (.*)--$/s)) != null) {
 63                         section = [ 'file', `${dir}/files/${trim(m[1]) || 'file'}`, '' ];
 64                 }
 65                 else if ((m = match(line, /^-- End( \(no-eol\))? --$/s)) != 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_]*)=(.*)$/s)) != 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                 `${join(' ', map(testcase.args ?? [], shellquote))} -`,
154                 `>/dev/fd/${fout.fileno()} 2>/dev/fd/${ferr.fileno()}`
155         ]);
156 
157         let proc = fs.popen(cmd, 'w') ?? die(`Error launching test command "${cmd}": ${fs.error()}\n`);
158 
159         if (testcase.code != null)
160                 proc.write(testcase.code);
161 
162         let exitcode = proc.close();
163 
164         fout.seek(0);
165         ferr.seek(0);
166 
167         let ok = true;
168 
169         if (replace(ferr.read('all'), dir, '.') != eerr) {
170                 if (ok) print('!\n');
171                 printf("Testcase #%d: Expected stderr did not match:\n", num);
172                 diff('stderr', eerr, ferr);
173                 print("---\n");
174                 ok = false;
175         }
176 
177         if (replace(fout.read('all'), dir, '.') != eout) {
178                 if (ok) print('!\n');
179                 printf("Testcase #%d: Expected stdout did not match:\n", num);
180                 diff('stdout', eout, fout);
181                 print("---\n");
182                 ok = false;
183         }
184 
185         if (ecode != null && exitcode != ecode) {
186                 if (ok) print('!\n');
187                 printf("Testcase #%d: Expected exit code did not match:\n", num);
188                 diff('code', `${ecode}\n`, `${exitcode}\n`);
189                 print("---\n");
190                 ok = false;
191         }
192 
193         return ok;
194 }
195 
196 function run_test(file) {
197         let name = fs.basename(file);
198         printf('%s %s ', name, substr(line, length(name)));
199 
200         let tmpdir = sprintf('/tmp/test.%d', getpid());
201         let testcases = parse_testcases(file, tmpdir);
202         let failed = 0;
203 
204         fs.mkdir(tmpdir);
205 
206         try {
207                 for (let i, testcase in testcases) {
208                         for (let path, data in testcase.files) {
209                                 mkdir_p(fs.dirname(path));
210                                 fs.writefile(path, data) ?? die(`Error writing testcase file "${path}": ${fs.error()}\n`);
211                         }
212 
213                         failed += !run_testcase(i + 1, tmpdir, testcase);
214                 }
215         }
216         catch (e) {
217                 warn(`${e.type}: ${e.message}\n${e.stacktrace[0].context}\n`);
218         }
219 
220         system(['rm', '-r', tmpdir]);
221 
222         if (failed == 0)
223                 print('OK\n');
224         else
225                 printf('%s %s FAILED (%d/%d)\n', name, substr(line, length(name)), failed, length(testcases));
226 
227         return failed;
228 }
229 
230 let n_tests = 0;
231 let n_fails = 0;
232 let select_tests = filter(map(ARGV, p => fs.realpath(p)), length);
233 
234 function use_test(input) {
235         return fs.access(input = fs.realpath(input)) &&
236                 (!length(select_tests) || filter(select_tests, p => p == input)[0]);
237 }
238 
239 for (let catdir in fs.glob(`${testdir}/[0-9][0-9]_*`)) {
240         if (fs.stat(catdir)?.type != 'directory')
241                 continue;
242 
243         printf('\n##\n## Running %s tests\n##\n\n', substr(fs.basename(catdir), 3));
244 
245         for (let testfile in fs.glob(`${catdir}/[0-9][0-9]_*`)) {
246                 if (!use_test(testfile)) continue;
247 
248                 n_tests++;
249                 n_fails += run_test(testfile);
250         }
251 }
252 
253 printf('\nRan %d tests, %d okay, %d failures\n', n_tests, n_tests - n_fails, n_fails);
254 exit(n_fails);

This page was automatically generated by LXR 0.3.1.  •  OpenWrt