authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-04-20 02:26:36-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-04-20 02:26:36-04:00
log8654bc18104d64c7a7f9f80bdba75ed4e0c005fa
tree398f7b9d18988b61ad12db21aa72e21da3aec4d4
parent1ff73a8e69a09b3bc993cffb755cc2ca98c7040b

delete test_artifacts directory when tests complete

* add std.os.deleteTree * add std.os.deleteDir * add std.os.page_size * add std.os API for iterating over directories * refactor duplication in build.zig * update documentation on how to run tests

9 files changed, 281 insertions(+), 50 deletions(-)

README.md+3-3
......@@ -45,8 +45,8 @@ compromises backward compatibility.
4545 * Release mode produces heavily optimized code. What other projects call
4646 "Link Time Optimization" Zig does automatically.
4747 * Mark functions as tests and automatically run them with `zig test`.
48 * Currently supported architectures: `x86_64`, `i386`
49 * Currently supported operating systems: linux, macosx
48 * Currently supported architectures: `x86_64`
49 * Currently supported operating systems: linux
5050 * Friendly toward package maintainers. Reproducible build, bootstrapping
5151 process carefully documented. Issues filed by package maintainers are
5252 considered especially important.
......@@ -103,7 +103,7 @@ cd build
103103cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=$(echo -n | cc -E -x c - -v 2>&1 | grep -B1 "End of search list." | head -n1 | cut -c 2- | sed "s/ .*//") -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbegin.o))
104104make
105105make install
106./run_tests
106./zig build --build-file ../build.zig test
107107```
108108
109109### Release / Install Build
build.zig+13-38
......@@ -5,44 +5,19 @@ pub fn build(b: &Builder) {
55 const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
66 const test_step = b.step("test", "Run all the tests");
77
8 const behavior_tests = b.step("test-behavior", "Run the behavior tests");
9 test_step.dependOn(behavior_tests);
10 for ([]bool{false, true}) |release| {
11 for ([]bool{false, true}) |link_libc| {
12 const these_tests = b.addTest("test/behavior.zig");
13 these_tests.setNamePrefix(b.fmt("behavior-{}-{} ",
14 if (release) "release" else "debug",
15 if (link_libc) "c" else "bare"));
16 these_tests.setFilter(test_filter);
17 these_tests.setRelease(release);
18 if (link_libc) {
19 these_tests.linkLibrary("c");
20 }
21 behavior_tests.dependOn(&these_tests.step);
22 }
23 }
8 const cleanup = b.addRemoveDirTree("test_artifacts");
9 test_step.dependOn(&cleanup.step);
2410
25 const std_lib_tests = b.step("test-std", "Run the standard library tests");
26 test_step.dependOn(std_lib_tests);
27 for ([]bool{false, true}) |release| {
28 for ([]bool{false, true}) |link_libc| {
29 const these_tests = b.addTest("std/index.zig");
30 these_tests.setNamePrefix(b.fmt("std-{}-{} ",
31 if (release) "release" else "debug",
32 if (link_libc) "c" else "bare"));
33 these_tests.setFilter(test_filter);
34 these_tests.setRelease(release);
35 if (link_libc) {
36 these_tests.linkLibrary("c");
37 }
38 std_lib_tests.dependOn(&these_tests.step);
39 }
40 }
11 cleanup.step.dependOn(tests.addPkgTests(b, test_filter,
12 "test/behavior.zig", "behavior", "Run the behavior tests"));
4113
42 test_step.dependOn(tests.addCompareOutputTests(b, test_filter));
43 test_step.dependOn(tests.addBuildExampleTests(b, test_filter));
44 test_step.dependOn(tests.addCompileErrorTests(b, test_filter));
45 test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter));
46 test_step.dependOn(tests.addDebugSafetyTests(b, test_filter));
47 test_step.dependOn(tests.addParseHTests(b, test_filter));
14 cleanup.step.dependOn(tests.addPkgTests(b, test_filter,
15 "std/index.zig", "std", "Run the standard library tests"));
16
17 cleanup.step.dependOn(tests.addCompareOutputTests(b, test_filter));
18 cleanup.step.dependOn(tests.addBuildExampleTests(b, test_filter));
19 cleanup.step.dependOn(tests.addCompileErrorTests(b, test_filter));
20 cleanup.step.dependOn(tests.addAssembleAndLinkTests(b, test_filter));
21 cleanup.step.dependOn(tests.addDebugSafetyTests(b, test_filter));
22 cleanup.step.dependOn(tests.addParseHTests(b, test_filter));
4823}
std/build.zig+34-2
......@@ -195,6 +195,12 @@ pub const Builder = struct {
195195 return log_step;
196196 }
197197
198 pub fn addRemoveDirTree(self: &Builder, dir_path: []const u8) -> &RemoveDirStep {
199 const remove_dir_step = %%self.allocator.create(RemoveDirStep);
200 *remove_dir_step = RemoveDirStep.init(self, dir_path);
201 return remove_dir_step;
202 }
203
198204 pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version {
199205 Version {
200206 .major = major,
......@@ -1548,10 +1554,12 @@ pub const WriteFileStep = struct {
15481554 const full_path = self.builder.pathFromRoot(self.file_path);
15491555 const full_path_dir = %%os.path.dirname(self.builder.allocator, full_path);
15501556 os.makePath(self.builder.allocator, full_path_dir) %% |err| {
1551 debug.panic("unable to make path {}: {}\n", full_path_dir, @errorName(err));
1557 %%io.stderr.printf("unable to make path {}: {}\n", full_path_dir, @errorName(err));
1558 return err;
15521559 };
15531560 io.writeFile(full_path, self.data, self.builder.allocator) %% |err| {
1554 debug.panic("unable to write {}: {}\n", full_path, @errorName(err));
1561 %%io.stderr.printf("unable to write {}: {}\n", full_path, @errorName(err));
1562 return err;
15551563 };
15561564 }
15571565};
......@@ -1576,6 +1584,30 @@ pub const LogStep = struct {
15761584 }
15771585};
15781586
1587pub const RemoveDirStep = struct {
1588 step: Step,
1589 builder: &Builder,
1590 dir_path: []const u8,
1591
1592 pub fn init(builder: &Builder, dir_path: []const u8) -> RemoveDirStep {
1593 return RemoveDirStep {
1594 .builder = builder,
1595 .step = Step.init(builder.fmt("RemoveDir {}", dir_path), builder.allocator, make),
1596 .dir_path = dir_path,
1597 };
1598 }
1599
1600 fn make(step: &Step) -> %void {
1601 const self = @fieldParentPtr(RemoveDirStep, "step", step);
1602
1603 const full_path = self.builder.pathFromRoot(self.dir_path);
1604 os.deleteTree(self.builder.allocator, full_path) %% |err| {
1605 %%io.stderr.printf("Unable to remove {}: {}\n", full_path, @errorName(err));
1606 return err;
1607 };
1608 }
1609};
1610
15791611pub const Step = struct {
15801612 name: []const u8,
15811613 makeFn: fn(self: &Step) -> %void,
std/io.zig+4-6
......@@ -57,8 +57,6 @@ error NoMem;
5757error Unseekable;
5858error Eof;
5959
60const buffer_size = 4 * 1024;
61
6260pub const OpenRead = 0b0001;
6361pub const OpenWrite = 0b0010;
6462pub const OpenCreate = 0b0100;
......@@ -66,7 +64,7 @@ pub const OpenTruncate = 0b1000;
6664
6765pub const OutStream = struct {
6866 fd: i32,
69 buffer: [buffer_size]u8,
67 buffer: [os.page_size]u8,
7068 index: usize,
7169
7270 /// `path` may need to be copied in memory to add a null terminating byte. In this case
......@@ -97,7 +95,7 @@ pub const OutStream = struct {
9795 }
9896
9997 pub fn write(self: &OutStream, bytes: []const u8) -> %void {
100 if (bytes.len >= buffer_size) {
98 if (bytes.len >= self.buffer.len) {
10199 %return self.flush();
102100 return os.posixWrite(self.fd, bytes);
103101 }
......@@ -329,7 +327,7 @@ pub const InStream = struct {
329327 }
330328
331329 pub fn readAll(is: &InStream, buf: &Buffer0) -> %void {
332 %return buf.resize(buffer_size);
330 %return buf.resize(os.page_size);
333331
334332 var actual_buf_len: usize = 0;
335333 while (true) {
......@@ -341,7 +339,7 @@ pub const InStream = struct {
341339 return buf.resize(actual_buf_len);
342340 }
343341
344 %return buf.resize(actual_buf_len + buffer_size);
342 %return buf.resize(actual_buf_len + os.page_size);
345343 }
346344 }
347345};
std/list.zig+9-1
......@@ -61,10 +61,15 @@ pub fn List(comptime T: type) -> type{
6161 l.len = new_length;
6262 return result;
6363 }
64
65 pub fn pop(self: &Self) -> T {
66 self.len -= 1;
67 return self.items[self.len];
68 }
6469 }
6570}
6671
67test "basicListTest" {
72test "basic list test" {
6873 var list = List(i32).init(&debug.global_allocator);
6974 defer list.deinit();
7075
......@@ -75,4 +80,7 @@ test "basicListTest" {
7580 {var i: usize = 0; while (i < 10; i += 1) {
7681 assert(list.items[i] == i32(i + 1));
7782 }}
83
84 assert(list.pop() == 10);
85 assert(list.len == 9);
7886}
std/mem.zig+2
......@@ -9,6 +9,8 @@ error NoMem;
99
1010pub const Allocator = struct {
1111 allocFn: fn (self: &Allocator, n: usize) -> %[]u8,
12 /// Note that old_mem may be a slice of length 0, in which case reallocFn
13 /// should simply call allocFn
1214 reallocFn: fn (self: &Allocator, old_mem: []u8, new_size: usize) -> %[]u8,
1315 freeFn: fn (self: &Allocator, mem: []u8),
1416
std/os/index.zig+178
......@@ -17,6 +17,8 @@ pub const line_sep = switch (@compileVar("os")) {
1717 else => "\n",
1818};
1919
20pub const page_size = 4 * 1024;
21
2022const debug = @import("../debug.zig");
2123const assert = debug.assert;
2224
......@@ -32,6 +34,7 @@ const cstr = @import("../cstr.zig");
3234
3335const io = @import("../io.zig");
3436const base64 = @import("../base64.zig");
37const List = @import("../list.zig").List;
3538
3639error Unexpected;
3740error SystemResources;
......@@ -46,6 +49,7 @@ error SymLinkLoop;
4649error ReadOnlyFileSystem;
4750error LinkQuotaExceeded;
4851error RenameAcrossMountPoints;
52error DirNotEmpty;
4953
5054/// Fills `buf` with random bytes. If linking against libc, this calls the
5155/// appropriate OS-specific library call. Otherwise it uses the zig standard
......@@ -585,3 +589,177 @@ pub fn makePath(allocator: &Allocator, full_path: []const u8) -> %void {
585589 // TODO stat the file and return an error if it's not a directory
586590 };
587591}
592
593/// Returns ::error.DirNotEmpty if the directory is not empty.
594/// To delete a directory recursively, see ::deleteTree
595pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) -> %void {
596 const path_buf = %return allocator.alloc(u8, dir_path.len + 1);
597 defer allocator.free(path_buf);
598
599 mem.copy(u8, path_buf, dir_path);
600 path_buf[dir_path.len] = 0;
601
602 const err = posix.getErrno(posix.rmdir(path_buf.ptr));
603 if (err > 0) {
604 return switch (err) {
605 errno.EACCES, errno.EPERM => error.AccessDenied,
606 errno.EBUSY => error.FileBusy,
607 errno.EFAULT, errno.EINVAL => unreachable,
608 errno.ELOOP => error.SymLinkLoop,
609 errno.ENAMETOOLONG => error.NameTooLong,
610 errno.ENOENT => error.FileNotFound,
611 errno.ENOMEM => error.SystemResources,
612 errno.ENOTDIR => error.NotDir,
613 errno.EEXIST, errno.ENOTEMPTY => error.DirNotEmpty,
614 errno.EROFS => error.ReadOnlyFileSystem,
615 else => error.Unexpected,
616 };
617 }
618}
619
620/// Whether ::full_path describes a symlink, file, or directory, this function
621/// removes it. If it cannot be removed because it is a non-empty directory,
622/// this function recursively removes its entries and then tries again.
623// TODO non-recursive implementation
624pub fn deleteTree(allocator: &Allocator, full_path: []const u8) -> %void {
625start_over:
626 // First, try deleting the item as a file. This way we don't follow sym links.
627 try (deleteFile(allocator, full_path)) {
628 return;
629 } else |err| {
630 if (err == error.FileNotFound)
631 return;
632 if (err != error.IsDir)
633 return err;
634 }
635 {
636 var dir = Dir.open(allocator, full_path) %% |err| {
637 if (err == error.FileNotFound)
638 return;
639 if (err == error.NotDir)
640 goto start_over;
641 return err;
642 };
643 defer dir.close();
644
645 var full_entry_buf = List(u8).init(allocator);
646 defer full_entry_buf.deinit();
647
648 while (true) {
649 const entry = (%return dir.next()) ?? break;
650
651 %return full_entry_buf.resize(full_path.len + entry.name.len + 1);
652 const full_entry_path = full_entry_buf.toSlice();
653 mem.copy(u8, full_entry_path, full_path);
654 full_entry_path[full_path.len] = '/';
655 mem.copy(u8, full_entry_path[full_path.len + 1...], entry.name);
656
657 %return deleteTree(allocator, full_entry_path);
658 }
659 }
660 return deleteDir(allocator, full_path);
661}
662
663pub const Dir = struct {
664 fd: i32,
665 allocator: &Allocator,
666 buf: []u8,
667 index: usize,
668 end_index: usize,
669
670 const LinuxEntry = extern struct {
671 d_ino: usize,
672 d_off: usize,
673 d_reclen: u16,
674 d_name: u8, // field address is the address of first byte of name
675 };
676
677 pub const Entry = struct {
678 name: []const u8,
679 kind: Kind,
680
681 pub const Kind = enum {
682 BlockDevice,
683 CharacterDevice,
684 Directory,
685 NamedPipe,
686 SymLink,
687 File,
688 UnixDomainSocket,
689 Unknown,
690 };
691 };
692
693 pub fn open(allocator: &Allocator, dir_path: []const u8) -> %Dir {
694 const fd = %return posixOpen(dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0, allocator);
695 return Dir {
696 .allocator = allocator,
697 .fd = fd,
698 .index = 0,
699 .end_index = 0,
700 .buf = []u8{},
701 };
702 }
703
704 pub fn close(self: &Dir) {
705 self.allocator.free(self.buf);
706 posixClose(self.fd);
707 }
708
709 /// Memory such as file names referenced in this returned entry becomes invalid
710 /// with subsequent calls to next, as well as when this ::Dir is deinitialized.
711 pub fn next(self: &Dir) -> %?Entry {
712 start_over:
713 if (self.index >= self.end_index) {
714 if (self.buf.len == 0) {
715 self.buf = %return self.allocator.alloc(u8, 2); //page_size);
716 }
717
718 while (true) {
719 const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len);
720 const err = linux.getErrno(result);
721 if (err > 0) {
722 switch (err) {
723 errno.EBADF, errno.EFAULT, errno.ENOTDIR => unreachable,
724 errno.EINVAL => {
725 self.buf = %return self.allocator.realloc(u8, self.buf, self.buf.len * 2);
726 continue;
727 },
728 else => return error.Unexpected,
729 };
730 }
731 if (result == 0)
732 return null;
733 self.index = 0;
734 self.end_index = result;
735 break;
736 }
737 }
738 const linux_entry = @ptrcast(&LinuxEntry, &self.buf[self.index]);
739 const next_index = self.index + linux_entry.d_reclen;
740 self.index = next_index;
741
742 const name = (&linux_entry.d_name)[0...cstr.len(&linux_entry.d_name)];
743
744 // skip . and .. entries
745 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
746 goto start_over;
747 }
748
749 const type_char = self.buf[next_index - 1];
750 const entry_kind = switch (type_char) {
751 posix.DT_BLK => Entry.Kind.BlockDevice,
752 posix.DT_CHR => Entry.Kind.CharacterDevice,
753 posix.DT_DIR => Entry.Kind.Directory,
754 posix.DT_FIFO => Entry.Kind.NamedPipe,
755 posix.DT_LNK => Entry.Kind.SymLink,
756 posix.DT_REG => Entry.Kind.File,
757 posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
758 else => Entry.Kind.Unknown,
759 };
760 return Entry {
761 .name = name,
762 .kind = entry_kind,
763 };
764 }
765};
std/os/linux.zig+17
......@@ -241,6 +241,15 @@ pub const AF_NFC = PF_NFC;
241241pub const AF_VSOCK = PF_VSOCK;
242242pub const AF_MAX = PF_MAX;
243243
244pub const DT_UNKNOWN = 0;
245pub const DT_FIFO = 1;
246pub const DT_CHR = 2;
247pub const DT_DIR = 4;
248pub const DT_BLK = 6;
249pub const DT_REG = 8;
250pub const DT_LNK = 10;
251pub const DT_SOCK = 12;
252pub const DT_WHT = 14;
244253
245254fn unsigned(s: i32) -> u32 { *@ptrcast(&u32, &s) }
246255fn signed(s: u32) -> i32 { *@ptrcast(&i32, &s) }
......@@ -273,6 +282,10 @@ pub fn getcwd(buf: &u8, size: usize) -> usize {
273282 arch.syscall2(arch.SYS_getcwd, usize(buf), size)
274283}
275284
285pub fn getdents(fd: i32, dirp: &u8, count: usize) -> usize {
286 arch.syscall3(arch.SYS_getdents, usize(fd), usize(dirp), usize(count))
287}
288
276289pub fn mkdir(path: &const u8, mode: usize) -> usize {
277290 arch.syscall2(arch.SYS_mkdir, usize(path), mode)
278291}
......@@ -291,6 +304,10 @@ pub fn read(fd: i32, buf: &u8, count: usize) -> usize {
291304 arch.syscall3(arch.SYS_read, usize(fd), usize(buf), count)
292305}
293306
307pub fn rmdir(path: &const u8) -> usize {
308 arch.syscall1(arch.SYS_rmdir, usize(path))
309}
310
294311pub fn symlink(existing: &const u8, new: &const u8) -> usize {
295312 arch.syscall2(arch.SYS_symlink, usize(existing), usize(new))
296313}
test/tests.zig+21
......@@ -103,6 +103,27 @@ pub fn addParseHTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Ste
103103 return cases.step;
104104}
105105
106pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8,
107 name:[] const u8, desc: []const u8) -> &build.Step
108{
109 const step = b.step(b.fmt("test-{}", name), desc);
110 for ([]bool{false, true}) |release| {
111 for ([]bool{false, true}) |link_libc| {
112 const these_tests = b.addTest(root_src);
113 these_tests.setNamePrefix(b.fmt("{}-{}-{} ", name,
114 if (release) "release" else "debug",
115 if (link_libc) "c" else "bare"));
116 these_tests.setFilter(test_filter);
117 these_tests.setRelease(release);
118 if (link_libc) {
119 these_tests.linkLibrary("c");
120 }
121 step.dependOn(&these_tests.step);
122 }
123 }
124 return step;
125}
126
106127pub const CompareOutputContext = struct {
107128 b: &build.Builder,
108129 step: &build.Step,