Created
April 28, 2020 23:52
-
-
Save mikdusan/10903298223cc284d146e8de68be887e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const os = std.os; | |
const testing = std.testing; | |
const warn = std.debug.warn; | |
test "empty" { | |
try depTokenizer("", ""); | |
} | |
test "empty lf" { | |
try depTokenizer("\n", ""); | |
} | |
test "empty cr" { | |
try depTokenizer("\r", ""); | |
} | |
test "empty cr-lf" { | |
try depTokenizer("\r\n", ""); | |
} | |
test "empty reverse_solidus" { | |
try depTokenizer("\\", ""); | |
} | |
test "target" { | |
try depTokenizer("foo.o:", "target = {foo.o}"); | |
} | |
test "target begin with lf" { | |
try depTokenizer("\nfoo.o:", "target = {foo.o}"); | |
} | |
test "target begin with cr" { | |
try depTokenizer("\rfoo.o:", "target = {foo.o}"); | |
} | |
test "target begin with cr-lf" { | |
try depTokenizer("\r\nfoo.o:", "target = {foo.o}"); | |
} | |
test "target begin with reverse_solidus" { | |
try depTokenizer("\\foo.o:", "target = {\\foo.o}"); | |
} | |
test "target begin with space" { | |
try depTokenizer(" foo.o:", "target = {foo.o}"); | |
} | |
test "target begin with tab" { | |
try depTokenizer("\tfoo.o:", "target = {foo.o}"); | |
} | |
test "target begin with reverse_solidus space" { | |
try depTokenizer("\\ foo.o:", "target = { foo.o}"); | |
} | |
test "target begin with escaped number_sign" { | |
try depTokenizer("\\#foo.o:", "target = {#foo.o}"); | |
} | |
test "target begin with escaped dollar_sign" { | |
try depTokenizer("$$foo.o:", "target = {$foo.o}"); | |
} | |
test "space prereq" { | |
try depTokenizer( | |
\\foo.o: foo.c | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c} | |
); | |
} | |
test "nospace preqreq" { | |
try depTokenizer( | |
\\foo.o:foo.c | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c} | |
); | |
} | |
test "lf prereq" { | |
try depTokenizer( | |
\\foo.o:\ | |
\\foo.c | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c} | |
); | |
} | |
test "reverse_solidus space prereq" { | |
try depTokenizer( | |
\\foo.o:\ foo.c | |
, | |
\\target = {foo.o} | |
\\illegal char ' ' for target at position 7: failed processing | |
); | |
} | |
test "reverse_solidus number_sign prereq" { | |
try depTokenizer( | |
\\foo.o:\#foo.c | |
, | |
\\target = {foo.o} | |
\\illegal char '#' for target at position 7: failed processing | |
); | |
} | |
test "prereq lf" { | |
try depTokenizer( | |
\\foo.o: foo.c | |
\\ | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c} | |
); | |
} | |
test "prereq reverse_solidus" { | |
try depTokenizer( | |
\\foo.o: foo.c\ | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c} | |
); | |
} | |
test "prereq reverse_solidus prereq" { | |
try depTokenizer( | |
\\foo.o: foo.c\ | |
\\ foo.h | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c} | |
\\prereq = {foo.h} | |
); | |
} | |
test "prereq reverse_solidus blank" { | |
try depTokenizer( | |
\\foo.o: foo.c\ | |
\\ | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c} | |
); | |
} | |
test "rhs dollar_sign" { | |
try depTokenizer( | |
\\foo.o:$ | |
, | |
\\target = {foo.o} | |
\\prereq = {$} | |
); | |
} | |
test "prereq dollar_sign" { | |
try depTokenizer( | |
\\foo.o: foo.c$ | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c$} | |
); | |
} | |
test "1 target multiline prereq clean" { | |
try depTokenizer( | |
\\foo.o: \ | |
\\ foo.c \ | |
\\ a.h \ | |
\\ b.h \ | |
\\ c.h | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c} | |
\\prereq = {a.h} | |
\\prereq = {b.h} | |
\\prereq = {c.h} | |
); | |
} | |
test "3 targets mixed prereq clean" { | |
try depTokenizer( | |
\\foo.o: foo.c | |
\\bar.o: bar.c a.h b.h c.h | |
\\abc.o: abc.c \ | |
\\ one.h two.h \ | |
\\ three.h four.h | |
, | |
\\target = {foo.o} | |
\\prereq = {foo.c} | |
\\target = {bar.o} | |
\\prereq = {bar.c} | |
\\prereq = {a.h} | |
\\prereq = {b.h} | |
\\prereq = {c.h} | |
\\target = {abc.o} | |
\\prereq = {abc.c} | |
\\prereq = {one.h} | |
\\prereq = {two.h} | |
\\prereq = {three.h} | |
\\prereq = {four.h} | |
); | |
} | |
test "mix it up" { | |
try depTokenizer( | |
\\ascii.o: ascii.c | |
\\base64.o: base64.c stdio.h | |
\\elf.o: elf.c a.h b.h c.h | |
\\macho.o:\ | |
\\ macho.c \ | |
\\ a.h b.h c.h | |
, | |
\\target = {ascii.o} | |
\\prereq = {ascii.c} | |
\\target = {base64.o} | |
\\prereq = {base64.c} | |
\\prereq = {stdio.h} | |
\\target = {elf.o} | |
\\prereq = {elf.c} | |
\\prereq = {a.h} | |
\\prereq = {b.h} | |
\\prereq = {c.h} | |
\\target = {macho.o} | |
\\prereq = {macho.c} | |
\\prereq = {a.h} | |
\\prereq = {b.h} | |
\\prereq = {c.h} | |
); | |
} | |
test "mix it up quotes" { | |
try depTokenizer( | |
\\a$$scii.o: ascii.c | |
\\\base64.o: "\base64.c" "s t#dio.h" | |
\\e\lf.o: "e\lf.c" "a.h$$" "$$b.h c.h$$" | |
\\macho.o:\ | |
\\ "macho!.c" \ | |
\\ a.h b.h c.h | |
, | |
\\target = {a$scii.o} | |
\\prereq = {ascii.c} | |
\\target = {\base64.o} | |
\\prereq = {\base64.c} | |
\\prereq = {s t#dio.h} | |
\\target = {e\lf.o} | |
\\prereq = {e\lf.c} | |
\\prereq = {a.h$$} | |
\\prereq = {$$b.h c.h$$} | |
\\target = {macho.o} | |
\\prereq = {macho!.c} | |
\\prereq = {a.h} | |
\\prereq = {b.h} | |
\\prereq = {c.h} | |
); | |
} | |
test "windows prereqs" { | |
try depTokenizer( | |
\\foo.o: "C:\Program Files (x86)\Microsoft Visual Studio\foo.c" | |
\\foo2.o: "C:\Program Files (x86)\Microsoft Visual Studio\foo2.c" \ | |
\\ "C:\Program Files (x86)\Microsoft Visual Studio\foo1.h" \ | |
\\ "C:\Program Files (x86)\Microsoft Visual Studio\foo2.h" | |
, | |
\\target = {foo.o} | |
\\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo.c} | |
\\target = {foo2.o} | |
\\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo2.c} | |
\\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo1.h} | |
\\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo2.h} | |
); | |
} | |
// - run test_dep_tokenizer executable against input and compare with output | |
// - create thread to feed childproc stdin and read childproc stdout | |
fn depTokenizer(input: []const u8, expect: []const u8) !void { | |
var arena_allocator = std.heap.ArenaAllocator.init(std.heap.c_allocator); | |
const arena = &arena_allocator.allocator; | |
// TODO: enable | |
// defers seem to mask the correct stack trace line in a test failure | |
// could be older lldb in use? | |
//defer arena_allocator.deinit(); | |
var args = std.ArrayList([]const u8).init(arena); | |
const exe = "test_dep_tokenizer"; | |
try args.append(exe); | |
const child = try os.ChildProcess.init(args.toSliceConst(), arena); | |
const StdIo = os.ChildProcess.StdIo; | |
child.stdin_behavior = StdIo.Pipe; | |
child.stdout_behavior = StdIo.Pipe; | |
child.spawn() catch |err| std.debug.panic("failed to spawn '{}': {}\n", exe, @errorName(err)); | |
var got: []const u8 = undefined; | |
const feeder = try os.spawnThread( | |
FeedTokenizerContext{ | |
.out = child.stdin.?, | |
.bytes = input, | |
}, | |
feedTokenizer, | |
); | |
{ | |
errdefer feeder.wait(); | |
var got_buffer = try std.Buffer.init(arena, ""); | |
var in_fos = child.stdout.?.inStream(); | |
var ins = &in_fos.stream; | |
var buf: [1024]u8 = undefined; | |
while (true) { | |
var len = try ins.read(buf[0..]); | |
try got_buffer.append(buf[0..len]); | |
if (len < buf.len) break; | |
} | |
got = got_buffer.toSliceConst(); | |
if (got.len > 0 and got[got.len-1] == '\n') { | |
got = got[0..got.len-1]; | |
} | |
} | |
feeder.wait(); | |
const term = child.wait() catch |err| std.debug.panic("failed to spawn '{}': {}\n", exe, @errorName(err)); | |
switch (term) { | |
.Exited => |code| { | |
if (code != 0) { | |
warn("process '{}' exited with error code {}\n", exe, code); | |
return error.TestFailed; | |
} | |
}, | |
else => { | |
warn("process '{}' terminated unexpectedly\n", exe); | |
return error.TestFailed; | |
} | |
} | |
if (std.mem.eql(u8, expect, got)) { | |
testing.expect(true); | |
return; | |
} | |
var out_file = try std.io.getStdErr(); | |
var out_fos = out_file.outStream(); | |
const out = &out_fos.stream; | |
try out.write("\n"); | |
try printSection(out, "<<<< input", input); | |
try printSection(out, "==== expect", expect); | |
try printSection(out, ">>>> got", got); | |
try printRule(out); | |
testing.expect(false); | |
} | |
fn printSection(context: var, label: []const u8, bytes: []const u8) !void { | |
try printLabel(context, label, bytes); | |
try hexDump(context, bytes); | |
try printRule(context); | |
try context.write(bytes); | |
if (bytes.len > 0 and bytes[bytes.len-1] != '\n') try context.write("\n"); | |
} | |
fn printLabel(context: var, label: []const u8, bytes: []const u8) !void { | |
var buf: [80]u8 = undefined; | |
var text = try std.fmt.bufPrint(buf[0..], "{} {} bytes ", label, bytes.len); | |
try context.write(text); | |
var i: usize = text.len; | |
const end = 79; | |
while (i < 79) : (i += 1) { | |
try context.writeByte(label[0]); | |
} | |
try context.write("\n"); | |
} | |
fn printRule(context: var) !void { | |
var i: usize = 0; | |
const end = 79; | |
while (i < 79) : (i += 1) { | |
try context.write("-"); | |
} | |
try context.write("\n"); | |
} | |
fn feedTokenizer(xt: FeedTokenizerContext) void { | |
var out_fos = xt.out.outStream(); | |
var out = &out_fos.stream; | |
out.write(xt.bytes) catch {}; | |
xt.out.close(); | |
} | |
const FeedTokenizerContext = struct { | |
out: os.File, | |
bytes: []const u8, | |
}; | |
fn hexDump(context: var, bytes: []const u8) !void { | |
const n16 = bytes.len >> 4; | |
var line: usize = 0; | |
var offset: usize = 0; | |
while (line < n16) : (line += 1) { | |
try hexDump16(context, offset, bytes[offset..offset+16]); | |
offset += 16; | |
} | |
const n = bytes.len & 0x0f; | |
if (n > 0) { | |
try printHexValue(context, offset, 8); | |
try context.write(":"); | |
try context.write(" "); | |
var end1 = std.math.min(offset+n, offset+8); | |
for (bytes[offset..end1]) |b| { | |
try context.write(" "); | |
try printHexValue(context, b, 2); | |
} | |
var end2 = offset + n; | |
if (end2 > end1) { | |
try context.write(" "); | |
for (bytes[end1..end2]) |b| { | |
try context.write(" "); | |
try printHexValue(context, b, 2); | |
} | |
} | |
const short = 16 - n; | |
var i: usize = 0; | |
while (i < short) : (i += 1) { | |
try context.write(" "); | |
} | |
if (end2 > end1) { | |
try context.write(" |"); | |
} else { | |
try context.write(" |"); | |
} | |
try printCharValues(context, bytes[offset..end2]); | |
try context.write("|\n"); | |
offset += n; | |
} | |
try printHexValue(context, offset, 8); | |
try context.write(":"); | |
try context.write("\n"); | |
} | |
fn hexDump16(context: var, offset: usize, bytes: []const u8) !void { | |
try printHexValue(context, offset, 8); | |
try context.write(":"); | |
try context.write(" "); | |
for (bytes[0..8]) |b| { | |
try context.write(" "); | |
try printHexValue(context, b, 2); | |
} | |
try context.write(" "); | |
for (bytes[8..16]) |b| { | |
try context.write(" "); | |
try printHexValue(context, b, 2); | |
} | |
try context.write(" |"); | |
try printCharValues(context, bytes); | |
try context.write("|\n"); | |
} | |
fn printHexValue(context: var, value: u64, width: u8) !void { | |
var buffer: [16]u8 = undefined; | |
const len = std.fmt.formatIntBuf(buffer[0..], value, 16, false, width); | |
try context.write(buffer[0..len]); | |
} | |
fn printCharValues(context: var, bytes: []const u8) !void { | |
for (bytes) |b| { | |
try context.writeByte(print_char_tab[b]); | |
} | |
} | |
const print_char_tab: []const u8 = | |
"................................ !\"#$%&'()*+,-./0123456789:;<=>?" ++ | |
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~." ++ | |
"................................................................" ++ | |
"................................................................" | |
; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment