mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 22:35:24 +00:00
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
This commit is contained in:
parent
1ff73a8e69
commit
8654bc1810
@ -45,8 +45,8 @@ compromises backward compatibility.
|
||||
* Release mode produces heavily optimized code. What other projects call
|
||||
"Link Time Optimization" Zig does automatically.
|
||||
* Mark functions as tests and automatically run them with `zig test`.
|
||||
* Currently supported architectures: `x86_64`, `i386`
|
||||
* Currently supported operating systems: linux, macosx
|
||||
* Currently supported architectures: `x86_64`
|
||||
* Currently supported operating systems: linux
|
||||
* Friendly toward package maintainers. Reproducible build, bootstrapping
|
||||
process carefully documented. Issues filed by package maintainers are
|
||||
considered especially important.
|
||||
@ -103,7 +103,7 @@ cd build
|
||||
cmake .. -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))
|
||||
make
|
||||
make install
|
||||
./run_tests
|
||||
./zig build --build-file ../build.zig test
|
||||
```
|
||||
|
||||
### Release / Install Build
|
||||
|
||||
51
build.zig
51
build.zig
@ -5,44 +5,19 @@ pub fn build(b: &Builder) {
|
||||
const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
|
||||
const test_step = b.step("test", "Run all the tests");
|
||||
|
||||
const behavior_tests = b.step("test-behavior", "Run the behavior tests");
|
||||
test_step.dependOn(behavior_tests);
|
||||
for ([]bool{false, true}) |release| {
|
||||
for ([]bool{false, true}) |link_libc| {
|
||||
const these_tests = b.addTest("test/behavior.zig");
|
||||
these_tests.setNamePrefix(b.fmt("behavior-{}-{} ",
|
||||
if (release) "release" else "debug",
|
||||
if (link_libc) "c" else "bare"));
|
||||
these_tests.setFilter(test_filter);
|
||||
these_tests.setRelease(release);
|
||||
if (link_libc) {
|
||||
these_tests.linkLibrary("c");
|
||||
}
|
||||
behavior_tests.dependOn(&these_tests.step);
|
||||
}
|
||||
}
|
||||
const cleanup = b.addRemoveDirTree("test_artifacts");
|
||||
test_step.dependOn(&cleanup.step);
|
||||
|
||||
const std_lib_tests = b.step("test-std", "Run the standard library tests");
|
||||
test_step.dependOn(std_lib_tests);
|
||||
for ([]bool{false, true}) |release| {
|
||||
for ([]bool{false, true}) |link_libc| {
|
||||
const these_tests = b.addTest("std/index.zig");
|
||||
these_tests.setNamePrefix(b.fmt("std-{}-{} ",
|
||||
if (release) "release" else "debug",
|
||||
if (link_libc) "c" else "bare"));
|
||||
these_tests.setFilter(test_filter);
|
||||
these_tests.setRelease(release);
|
||||
if (link_libc) {
|
||||
these_tests.linkLibrary("c");
|
||||
}
|
||||
std_lib_tests.dependOn(&these_tests.step);
|
||||
}
|
||||
}
|
||||
cleanup.step.dependOn(tests.addPkgTests(b, test_filter,
|
||||
"test/behavior.zig", "behavior", "Run the behavior tests"));
|
||||
|
||||
test_step.dependOn(tests.addCompareOutputTests(b, test_filter));
|
||||
test_step.dependOn(tests.addBuildExampleTests(b, test_filter));
|
||||
test_step.dependOn(tests.addCompileErrorTests(b, test_filter));
|
||||
test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter));
|
||||
test_step.dependOn(tests.addDebugSafetyTests(b, test_filter));
|
||||
test_step.dependOn(tests.addParseHTests(b, test_filter));
|
||||
cleanup.step.dependOn(tests.addPkgTests(b, test_filter,
|
||||
"std/index.zig", "std", "Run the standard library tests"));
|
||||
|
||||
cleanup.step.dependOn(tests.addCompareOutputTests(b, test_filter));
|
||||
cleanup.step.dependOn(tests.addBuildExampleTests(b, test_filter));
|
||||
cleanup.step.dependOn(tests.addCompileErrorTests(b, test_filter));
|
||||
cleanup.step.dependOn(tests.addAssembleAndLinkTests(b, test_filter));
|
||||
cleanup.step.dependOn(tests.addDebugSafetyTests(b, test_filter));
|
||||
cleanup.step.dependOn(tests.addParseHTests(b, test_filter));
|
||||
}
|
||||
|
||||
@ -195,6 +195,12 @@ pub const Builder = struct {
|
||||
return log_step;
|
||||
}
|
||||
|
||||
pub fn addRemoveDirTree(self: &Builder, dir_path: []const u8) -> &RemoveDirStep {
|
||||
const remove_dir_step = %%self.allocator.create(RemoveDirStep);
|
||||
*remove_dir_step = RemoveDirStep.init(self, dir_path);
|
||||
return remove_dir_step;
|
||||
}
|
||||
|
||||
pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version {
|
||||
Version {
|
||||
.major = major,
|
||||
@ -1548,10 +1554,12 @@ pub const WriteFileStep = struct {
|
||||
const full_path = self.builder.pathFromRoot(self.file_path);
|
||||
const full_path_dir = %%os.path.dirname(self.builder.allocator, full_path);
|
||||
os.makePath(self.builder.allocator, full_path_dir) %% |err| {
|
||||
debug.panic("unable to make path {}: {}\n", full_path_dir, @errorName(err));
|
||||
%%io.stderr.printf("unable to make path {}: {}\n", full_path_dir, @errorName(err));
|
||||
return err;
|
||||
};
|
||||
io.writeFile(full_path, self.data, self.builder.allocator) %% |err| {
|
||||
debug.panic("unable to write {}: {}\n", full_path, @errorName(err));
|
||||
%%io.stderr.printf("unable to write {}: {}\n", full_path, @errorName(err));
|
||||
return err;
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -1576,6 +1584,30 @@ pub const LogStep = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const RemoveDirStep = struct {
|
||||
step: Step,
|
||||
builder: &Builder,
|
||||
dir_path: []const u8,
|
||||
|
||||
pub fn init(builder: &Builder, dir_path: []const u8) -> RemoveDirStep {
|
||||
return RemoveDirStep {
|
||||
.builder = builder,
|
||||
.step = Step.init(builder.fmt("RemoveDir {}", dir_path), builder.allocator, make),
|
||||
.dir_path = dir_path,
|
||||
};
|
||||
}
|
||||
|
||||
fn make(step: &Step) -> %void {
|
||||
const self = @fieldParentPtr(RemoveDirStep, "step", step);
|
||||
|
||||
const full_path = self.builder.pathFromRoot(self.dir_path);
|
||||
os.deleteTree(self.builder.allocator, full_path) %% |err| {
|
||||
%%io.stderr.printf("Unable to remove {}: {}\n", full_path, @errorName(err));
|
||||
return err;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Step = struct {
|
||||
name: []const u8,
|
||||
makeFn: fn(self: &Step) -> %void,
|
||||
|
||||
10
std/io.zig
10
std/io.zig
@ -57,8 +57,6 @@ error NoMem;
|
||||
error Unseekable;
|
||||
error Eof;
|
||||
|
||||
const buffer_size = 4 * 1024;
|
||||
|
||||
pub const OpenRead = 0b0001;
|
||||
pub const OpenWrite = 0b0010;
|
||||
pub const OpenCreate = 0b0100;
|
||||
@ -66,7 +64,7 @@ pub const OpenTruncate = 0b1000;
|
||||
|
||||
pub const OutStream = struct {
|
||||
fd: i32,
|
||||
buffer: [buffer_size]u8,
|
||||
buffer: [os.page_size]u8,
|
||||
index: usize,
|
||||
|
||||
/// `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 {
|
||||
}
|
||||
|
||||
pub fn write(self: &OutStream, bytes: []const u8) -> %void {
|
||||
if (bytes.len >= buffer_size) {
|
||||
if (bytes.len >= self.buffer.len) {
|
||||
%return self.flush();
|
||||
return os.posixWrite(self.fd, bytes);
|
||||
}
|
||||
@ -329,7 +327,7 @@ pub const InStream = struct {
|
||||
}
|
||||
|
||||
pub fn readAll(is: &InStream, buf: &Buffer0) -> %void {
|
||||
%return buf.resize(buffer_size);
|
||||
%return buf.resize(os.page_size);
|
||||
|
||||
var actual_buf_len: usize = 0;
|
||||
while (true) {
|
||||
@ -341,7 +339,7 @@ pub const InStream = struct {
|
||||
return buf.resize(actual_buf_len);
|
||||
}
|
||||
|
||||
%return buf.resize(actual_buf_len + buffer_size);
|
||||
%return buf.resize(actual_buf_len + os.page_size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
10
std/list.zig
10
std/list.zig
@ -61,10 +61,15 @@ pub fn List(comptime T: type) -> type{
|
||||
l.len = new_length;
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn pop(self: &Self) -> T {
|
||||
self.len -= 1;
|
||||
return self.items[self.len];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "basicListTest" {
|
||||
test "basic list test" {
|
||||
var list = List(i32).init(&debug.global_allocator);
|
||||
defer list.deinit();
|
||||
|
||||
@ -75,4 +80,7 @@ test "basicListTest" {
|
||||
{var i: usize = 0; while (i < 10; i += 1) {
|
||||
assert(list.items[i] == i32(i + 1));
|
||||
}}
|
||||
|
||||
assert(list.pop() == 10);
|
||||
assert(list.len == 9);
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ error NoMem;
|
||||
|
||||
pub const Allocator = struct {
|
||||
allocFn: fn (self: &Allocator, n: usize) -> %[]u8,
|
||||
/// Note that old_mem may be a slice of length 0, in which case reallocFn
|
||||
/// should simply call allocFn
|
||||
reallocFn: fn (self: &Allocator, old_mem: []u8, new_size: usize) -> %[]u8,
|
||||
freeFn: fn (self: &Allocator, mem: []u8),
|
||||
|
||||
|
||||
178
std/os/index.zig
178
std/os/index.zig
@ -17,6 +17,8 @@ pub const line_sep = switch (@compileVar("os")) {
|
||||
else => "\n",
|
||||
};
|
||||
|
||||
pub const page_size = 4 * 1024;
|
||||
|
||||
const debug = @import("../debug.zig");
|
||||
const assert = debug.assert;
|
||||
|
||||
@ -32,6 +34,7 @@ const cstr = @import("../cstr.zig");
|
||||
|
||||
const io = @import("../io.zig");
|
||||
const base64 = @import("../base64.zig");
|
||||
const List = @import("../list.zig").List;
|
||||
|
||||
error Unexpected;
|
||||
error SystemResources;
|
||||
@ -46,6 +49,7 @@ error SymLinkLoop;
|
||||
error ReadOnlyFileSystem;
|
||||
error LinkQuotaExceeded;
|
||||
error RenameAcrossMountPoints;
|
||||
error DirNotEmpty;
|
||||
|
||||
/// Fills `buf` with random bytes. If linking against libc, this calls the
|
||||
/// 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 {
|
||||
// TODO stat the file and return an error if it's not a directory
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns ::error.DirNotEmpty if the directory is not empty.
|
||||
/// To delete a directory recursively, see ::deleteTree
|
||||
pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) -> %void {
|
||||
const path_buf = %return allocator.alloc(u8, dir_path.len + 1);
|
||||
defer allocator.free(path_buf);
|
||||
|
||||
mem.copy(u8, path_buf, dir_path);
|
||||
path_buf[dir_path.len] = 0;
|
||||
|
||||
const err = posix.getErrno(posix.rmdir(path_buf.ptr));
|
||||
if (err > 0) {
|
||||
return switch (err) {
|
||||
errno.EACCES, errno.EPERM => error.AccessDenied,
|
||||
errno.EBUSY => error.FileBusy,
|
||||
errno.EFAULT, errno.EINVAL => unreachable,
|
||||
errno.ELOOP => error.SymLinkLoop,
|
||||
errno.ENAMETOOLONG => error.NameTooLong,
|
||||
errno.ENOENT => error.FileNotFound,
|
||||
errno.ENOMEM => error.SystemResources,
|
||||
errno.ENOTDIR => error.NotDir,
|
||||
errno.EEXIST, errno.ENOTEMPTY => error.DirNotEmpty,
|
||||
errno.EROFS => error.ReadOnlyFileSystem,
|
||||
else => error.Unexpected,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether ::full_path describes a symlink, file, or directory, this function
|
||||
/// removes it. If it cannot be removed because it is a non-empty directory,
|
||||
/// this function recursively removes its entries and then tries again.
|
||||
// TODO non-recursive implementation
|
||||
pub fn deleteTree(allocator: &Allocator, full_path: []const u8) -> %void {
|
||||
start_over:
|
||||
// First, try deleting the item as a file. This way we don't follow sym links.
|
||||
try (deleteFile(allocator, full_path)) {
|
||||
return;
|
||||
} else |err| {
|
||||
if (err == error.FileNotFound)
|
||||
return;
|
||||
if (err != error.IsDir)
|
||||
return err;
|
||||
}
|
||||
{
|
||||
var dir = Dir.open(allocator, full_path) %% |err| {
|
||||
if (err == error.FileNotFound)
|
||||
return;
|
||||
if (err == error.NotDir)
|
||||
goto start_over;
|
||||
return err;
|
||||
};
|
||||
defer dir.close();
|
||||
|
||||
var full_entry_buf = List(u8).init(allocator);
|
||||
defer full_entry_buf.deinit();
|
||||
|
||||
while (true) {
|
||||
const entry = (%return dir.next()) ?? break;
|
||||
|
||||
%return full_entry_buf.resize(full_path.len + entry.name.len + 1);
|
||||
const full_entry_path = full_entry_buf.toSlice();
|
||||
mem.copy(u8, full_entry_path, full_path);
|
||||
full_entry_path[full_path.len] = '/';
|
||||
mem.copy(u8, full_entry_path[full_path.len + 1...], entry.name);
|
||||
|
||||
%return deleteTree(allocator, full_entry_path);
|
||||
}
|
||||
}
|
||||
return deleteDir(allocator, full_path);
|
||||
}
|
||||
|
||||
pub const Dir = struct {
|
||||
fd: i32,
|
||||
allocator: &Allocator,
|
||||
buf: []u8,
|
||||
index: usize,
|
||||
end_index: usize,
|
||||
|
||||
const LinuxEntry = extern struct {
|
||||
d_ino: usize,
|
||||
d_off: usize,
|
||||
d_reclen: u16,
|
||||
d_name: u8, // field address is the address of first byte of name
|
||||
};
|
||||
|
||||
pub const Entry = struct {
|
||||
name: []const u8,
|
||||
kind: Kind,
|
||||
|
||||
pub const Kind = enum {
|
||||
BlockDevice,
|
||||
CharacterDevice,
|
||||
Directory,
|
||||
NamedPipe,
|
||||
SymLink,
|
||||
File,
|
||||
UnixDomainSocket,
|
||||
Unknown,
|
||||
};
|
||||
};
|
||||
|
||||
pub fn open(allocator: &Allocator, dir_path: []const u8) -> %Dir {
|
||||
const fd = %return posixOpen(dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0, allocator);
|
||||
return Dir {
|
||||
.allocator = allocator,
|
||||
.fd = fd,
|
||||
.index = 0,
|
||||
.end_index = 0,
|
||||
.buf = []u8{},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: &Dir) {
|
||||
self.allocator.free(self.buf);
|
||||
posixClose(self.fd);
|
||||
}
|
||||
|
||||
/// Memory such as file names referenced in this returned entry becomes invalid
|
||||
/// with subsequent calls to next, as well as when this ::Dir is deinitialized.
|
||||
pub fn next(self: &Dir) -> %?Entry {
|
||||
start_over:
|
||||
if (self.index >= self.end_index) {
|
||||
if (self.buf.len == 0) {
|
||||
self.buf = %return self.allocator.alloc(u8, 2); //page_size);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len);
|
||||
const err = linux.getErrno(result);
|
||||
if (err > 0) {
|
||||
switch (err) {
|
||||
errno.EBADF, errno.EFAULT, errno.ENOTDIR => unreachable,
|
||||
errno.EINVAL => {
|
||||
self.buf = %return self.allocator.realloc(u8, self.buf, self.buf.len * 2);
|
||||
continue;
|
||||
},
|
||||
else => return error.Unexpected,
|
||||
};
|
||||
}
|
||||
if (result == 0)
|
||||
return null;
|
||||
self.index = 0;
|
||||
self.end_index = result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const linux_entry = @ptrcast(&LinuxEntry, &self.buf[self.index]);
|
||||
const next_index = self.index + linux_entry.d_reclen;
|
||||
self.index = next_index;
|
||||
|
||||
const name = (&linux_entry.d_name)[0...cstr.len(&linux_entry.d_name)];
|
||||
|
||||
// skip . and .. entries
|
||||
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
||||
goto start_over;
|
||||
}
|
||||
|
||||
const type_char = self.buf[next_index - 1];
|
||||
const entry_kind = switch (type_char) {
|
||||
posix.DT_BLK => Entry.Kind.BlockDevice,
|
||||
posix.DT_CHR => Entry.Kind.CharacterDevice,
|
||||
posix.DT_DIR => Entry.Kind.Directory,
|
||||
posix.DT_FIFO => Entry.Kind.NamedPipe,
|
||||
posix.DT_LNK => Entry.Kind.SymLink,
|
||||
posix.DT_REG => Entry.Kind.File,
|
||||
posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
|
||||
else => Entry.Kind.Unknown,
|
||||
};
|
||||
return Entry {
|
||||
.name = name,
|
||||
.kind = entry_kind,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@ -241,6 +241,15 @@ pub const AF_NFC = PF_NFC;
|
||||
pub const AF_VSOCK = PF_VSOCK;
|
||||
pub const AF_MAX = PF_MAX;
|
||||
|
||||
pub const DT_UNKNOWN = 0;
|
||||
pub const DT_FIFO = 1;
|
||||
pub const DT_CHR = 2;
|
||||
pub const DT_DIR = 4;
|
||||
pub const DT_BLK = 6;
|
||||
pub const DT_REG = 8;
|
||||
pub const DT_LNK = 10;
|
||||
pub const DT_SOCK = 12;
|
||||
pub const DT_WHT = 14;
|
||||
|
||||
fn unsigned(s: i32) -> u32 { *@ptrcast(&u32, &s) }
|
||||
fn signed(s: u32) -> i32 { *@ptrcast(&i32, &s) }
|
||||
@ -273,6 +282,10 @@ pub fn getcwd(buf: &u8, size: usize) -> usize {
|
||||
arch.syscall2(arch.SYS_getcwd, usize(buf), size)
|
||||
}
|
||||
|
||||
pub fn getdents(fd: i32, dirp: &u8, count: usize) -> usize {
|
||||
arch.syscall3(arch.SYS_getdents, usize(fd), usize(dirp), usize(count))
|
||||
}
|
||||
|
||||
pub fn mkdir(path: &const u8, mode: usize) -> usize {
|
||||
arch.syscall2(arch.SYS_mkdir, usize(path), mode)
|
||||
}
|
||||
@ -291,6 +304,10 @@ pub fn read(fd: i32, buf: &u8, count: usize) -> usize {
|
||||
arch.syscall3(arch.SYS_read, usize(fd), usize(buf), count)
|
||||
}
|
||||
|
||||
pub fn rmdir(path: &const u8) -> usize {
|
||||
arch.syscall1(arch.SYS_rmdir, usize(path))
|
||||
}
|
||||
|
||||
pub fn symlink(existing: &const u8, new: &const u8) -> usize {
|
||||
arch.syscall2(arch.SYS_symlink, usize(existing), usize(new))
|
||||
}
|
||||
|
||||
@ -103,6 +103,27 @@ pub fn addParseHTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Ste
|
||||
return cases.step;
|
||||
}
|
||||
|
||||
pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8,
|
||||
name:[] const u8, desc: []const u8) -> &build.Step
|
||||
{
|
||||
const step = b.step(b.fmt("test-{}", name), desc);
|
||||
for ([]bool{false, true}) |release| {
|
||||
for ([]bool{false, true}) |link_libc| {
|
||||
const these_tests = b.addTest(root_src);
|
||||
these_tests.setNamePrefix(b.fmt("{}-{}-{} ", name,
|
||||
if (release) "release" else "debug",
|
||||
if (link_libc) "c" else "bare"));
|
||||
these_tests.setFilter(test_filter);
|
||||
these_tests.setRelease(release);
|
||||
if (link_libc) {
|
||||
these_tests.linkLibrary("c");
|
||||
}
|
||||
step.dependOn(&these_tests.step);
|
||||
}
|
||||
}
|
||||
return step;
|
||||
}
|
||||
|
||||
pub const CompareOutputContext = struct {
|
||||
b: &build.Builder,
|
||||
step: &build.Step,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user