Skip to content

Instantly share code, notes, and snippets.

@mikdusan
Created April 28, 2020 23:52
Show Gist options
  • Save mikdusan/10903298223cc284d146e8de68be887e to your computer and use it in GitHub Desktop.
Save mikdusan/10903298223cc284d146e8de68be887e to your computer and use it in GitHub Desktop.
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