Skip to content

Instantly share code, notes, and snippets.

@cartr
Last active June 22, 2020 10:30
Show Gist options
  • Save cartr/8a1ea5b3a889686360c903cfa5339a84 to your computer and use it in GitHub Desktop.
Save cartr/8a1ea5b3a889686360c903cfa5339a84 to your computer and use it in GitHub Desktop.
const std = @import("std");
const TestCase = struct {
input: [:0]const u8, output: []const []const u8
};
// Test cases from https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=vs-2019#results-of-parsing-command-lines
// "f.exe " added to some tests to satisfy CommandLineToArgvW's assumption
// about the initial argument; Zig parser passes these tests without doing this
const MICROSOFT_TEST_CASES = [_]TestCase{
.{
.output = &[_][]const u8{ "a b c", "d", "e" },
.input =
\\"a b c" d e
},
.{
.output = &[_][]const u8{ "f.exe", "ab\"c", "\\", "d" },
.input =
\\f.exe "ab\"c" "\\" d
},
.{
.output = &[_][]const u8{ "a\\\\\\b", "de fg", "h" },
.input =
\\a\\\b d"e f"g h
},
.{
.output = &[_][]const u8{ "f.exe", "a\\\"b", "c", "d" },
.input =
\\f.exe a\\\"b c d
},
.{
.output = &[_][]const u8{ "f.exe", "a\\\\b c", "d", "e" },
.input =
\\f.exe a\\\\"b c" d e
},
};
// Test cases from https://github.com/ziglang/zig/blob/d907f574e02aadf8196e616bcc2fb2813cf2c82c/lib/std/process.zig#L575-L590
// "f.exe " added to some tests to satisfy CommandLineToArgvW's assumption
// about the initial argument
const ZIG_TEST_CASES = [_]TestCase{
.{
.input = "a b\tc d",
.output = &[_][]const u8{ "a", "b", "c", "d" },
},
.{
.input = "\"abc\" d e",
.output = &[_][]const u8{ "abc", "d", "e" },
},
.{
.input = "a\\\\\\b d\"e f\"g h",
.output = &[_][]const u8{ "a\\\\\\b", "de fg", "h" },
},
.{
.input = "f.exe a\\\\\\\"b c d",
.output = &[_][]const u8{ "f.exe", "a\\\"b", "c", "d" },
},
.{
.input = "f.exe a\\\\\\\\\"b c\" d e",
.output = &[_][]const u8{ "f.exe", "a\\\\b c", "d", "e" },
},
// Neither CommandLineToArgvW nor C's argv implement this "unclosed quotes\
// are literal" behavior; not sure why Zig does this
//.{
// .input = "a b\tc \"d f",
// .output = &[_][]const u8{ "a", "b", "c", "\"d", "f" }
//},
.{
.input = "\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"",
.output = &[_][]const u8{
".\\..\\zig-cache\\build",
"bin\\zig.exe",
".\\..",
".\\..\\zig-cache",
"--help",
},
},
};
const TEST_CASES = MICROSOFT_TEST_CASES ++ ZIG_TEST_CASES;
test "Zig argument parsing is correct" {
for (TEST_CASES) |case| {
var it = std.process.ArgIteratorWindows.initWithCmdLine(case.input.ptr);
for (case.output) |expected_arg| {
const arg = it.next(std.testing.allocator).? catch unreachable;
defer std.testing.allocator.free(arg);
std.testing.expectEqualSlices(u8, expected_arg, arg);
}
std.testing.expect(it.next(std.testing.allocator) == null);
}
}
pub extern "shell32" fn CommandLineToArgvW(lpCmdLine: std.os.windows.LPCWSTR, out_pNumArgs: *c_int) callconv(.Stdcall) ?[*]std.os.windows.LPWSTR;
test "CommandLineToArgvW parses arguments correctly" {
for (TEST_CASES) |case| {
var num_args: c_int = undefined;
var input_utf16 = try std.unicode.utf8ToUtf16LeWithNull(std.testing.allocator, case.input);
defer std.testing.allocator.free(input_utf16);
var args = CommandLineToArgvW(input_utf16, &num_args).?;
var i: usize = 0;
while (i < num_args) {
var arg_utf8 = try std.unicode.utf16leToUtf8Alloc(std.testing.allocator, std.mem.span(args[i]));
defer std.testing.allocator.free(arg_utf8);
std.testing.expectEqualSlices(u8, case.output[i], arg_utf8);
i += 1;
}
std.testing.expectEqual(num_args, @intCast(c_int, case.output.len));
}
}
// Adapted from https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#examples
#include <windows.h>
#include <stdio.h>
#include <shellapi.h>
int __cdecl main(int argc, char* argv[])
{
LPWSTR* szArglist;
int nArgs;
int i;
wprintf(L"C runtime parse:\n");
for (i = 0; i < argc; i++) printf("%d: %s\n", i, argv[i]);
wprintf(L"CommandLineToArgvW parse:\n");
szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
if (NULL == szArglist)
{
wprintf(L"CommandLineToArgvW failed\n");
return 0;
}
else for (i = 0; i < nArgs; i++) printf("%d: %ws\n", i, szArglist[i]);
// Free memory allocated for CommandLineToArgvW arguments.
LocalFree(szArglist);
return(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment