Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = .zig_utils,
.version = "0.7.1",
.version = "0.7.2",
.fingerprint = 0x6dc482caf73c4a75,
.minimum_zig_version = "0.16.0",

Expand Down
15 changes: 9 additions & 6 deletions src/floyd_warshall_optimized.zig
Original file line number Diff line number Diff line change
Expand Up @@ -488,12 +488,15 @@ pub fn FloydWarshallOptimized(comptime config: Config) type {
self.processRowSimd(k, i);
}

// Signal that iteration k is complete for our rows.
// Only one thread owns each row k (ranges are non-overlapping).
// That thread signals by adding thread_count to unblock all waiting threads.
if (k >= start_row and k < end_row) {
_ = sync_counters[k + 1].fetchAdd(thread_count_u32, .release);
}
// Full barrier: EVERY thread signals completion of iteration k,
// and iteration k+1 only starts once all `thread_count` threads
// have arrived (counter reaches thread_count). The previous code
// had only the owner of row k signal (adding thread_count once),
// which is NOT a barrier — a fast thread could enter iteration
// k+1 and read the next pivot row (k+1) while ITS owner was still
// updating it in iteration k, producing a `dist`/`next` matrix
// that's mutually inconsistent (cycles in `next`). See #13.
_ = sync_counters[k + 1].fetchAdd(1, .release);
}
}

Expand Down
45 changes: 45 additions & 0 deletions tests/root.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const std = @import("std");
const zig_utils = @import("zig_utils");

pub const vector_test = @import("vector_test.zig");
pub const quad_tree_test = @import("quad_tree_test.zig");
Expand All @@ -14,3 +15,47 @@ pub const zon_coercion_test = @import("zon_coercion_test.zig");
test {
std.testing.refAllDecls(@This());
}

// Regression for #13 — kept at the test ROOT (top-level) on purpose: the
// zspec runner does not currently execute tests nested inside the `*Spec`
// structs under `zig build test`, so a nested test would compile but never
// run. The parallel `generate()` barrier was not a full barrier, so threads
// read a not-yet-finalized pivot row and corrupted `dist`/`next` into cycles
// (→ infinite path reconstruction, hang + OOM on a 1000-worker colony). This
// asserts the parallel result is bit-identical to the scalar reference.
test "parallel Floyd-Warshall matches scalar reference (issue #13)" {
const allocator = std.testing.allocator;
const FloydWarshallParallel = zig_utils.FloydWarshallParallel;
const FloydWarshallScalar = zig_utils.FloydWarshallScalar;
const N: u32 = 256; // > 64 → parallel path on multi-core
const matrix = @as(usize, N) * @as(usize, N);

for (0..24) |seed| {
var par = FloydWarshallParallel.init(allocator);
defer par.deinit();
var scal = FloydWarshallScalar.init(allocator);
defer scal.deinit();
par.resize(N);
try par.clean();
scal.resize(N);
try scal.clean();
Comment on lines +33 to +41

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Initializing and resizing the par and scal instances inside the loop causes redundant allocations and deallocations of the dist and next matrices (each of size 256 * 256 * 4 bytes = 256 KB) on every iteration.

Since clean() resets the state and reuses the allocated matrices when the capacity is sufficient, we can optimize this by moving the initialization and resizing of par and scal outside the loop.

    var par = FloydWarshallParallel.init(allocator);
    defer par.deinit();
    var scal = FloydWarshallScalar.init(allocator);
    defer scal.deinit();
    par.resize(N);
    scal.resize(N);

    for (0..24) |seed| {
        try par.clean();
        try scal.clean();


var rng = std.Random.DefaultPrng.init(0xF00D +% seed);
const r = rng.random();
var e: usize = 0;
while (e < N * 4) : (e += 1) {
const u = r.intRangeLessThan(u32, 0, N);
const v = r.intRangeLessThan(u32, 0, N);
if (u == v) continue;
const w = r.intRangeLessThan(u32, 1, 20);
par.addEdge(u, v, w);
scal.addEdge(u, v, w);
}

par.generate();
scal.generate();

try std.testing.expectEqualSlices(u32, scal.dist[0..matrix], par.dist[0..matrix]);
try std.testing.expectEqualSlices(u32, scal.next[0..matrix], par.next[0..matrix]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

// Library module — link_libc=true because runner.zig/junit.zig use
// std.c.{open,write,close,getenv} on POSIX (and Win32 directly on
// Windows). Without this, downstream test binaries that import zspec
// get a libc-link error on Linux.
const zspec_mod = b.addModule("zspec", .{
.root_source_file = b.path("src/zspec.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});

// Optional ECS integration module
const zspec_ecs_mod = b.addModule("zspec-ecs", .{
.root_source_file = b.path("src/integrations/ecs.zig"),
.target = target,
.optimize = optimize,
});

// Optional FSM integration module
const zspec_fsm_mod = b.addModule("zspec-fsm", .{
.root_source_file = b.path("src/integrations/fsm.zig"),
.target = target,
.optimize = optimize,
});

// Unit tests for zspec itself
const lib_unit_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/zspec.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});

const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);

// Unit tests for the JUnit XML writer. Lives in its own test exe because
// `src/runner.zig` (used as the test_runner for the example/factory test
// suites) also imports `junit.zig`; pulling it in via `src/zspec.zig`
// would make the same file belong to both the `root` and `zspec` modules.
const junit_unit_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/junit.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});

const run_junit_unit_tests = b.addRunArtifact(junit_unit_tests);

// Example tests using zspec
const example_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("tests/example_test.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "zspec", .module = zspec_mod },
},
}),
.test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple },
});

const run_example_tests = b.addRunArtifact(example_tests);

// Factory union tests (issue #29)
const factory_union_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("tests/factory_union_test.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "zspec", .module = zspec_mod },
},
}),
.test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple },
});

const run_factory_union_tests = b.addRunArtifact(factory_union_tests);

// Factory .zon loading tests (issue #31)
const factory_zon_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("tests/factory_zon_test.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "zspec", .module = zspec_mod },
},
}),
.test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple },
});

const run_factory_zon_tests = b.addRunArtifact(factory_zon_tests);

// Fixture tests (RFC 001 / issue #38)
const fixture_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("tests/fixture_test.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "zspec", .module = zspec_mod },
},
}),
.test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple },
});

const run_fixture_tests = b.addRunArtifact(fixture_tests);

// Regression test for issue #44 — the `.simple` runner must
// initialize `std.testing.io_instance` so tests reaching for
// `std.testing.io` don't deadlock on linux. Routed through the
// `.simple` runner (which contains the fix) on purpose: a
// default-runner version of this test would always pass because
// the stdlib's runner does the per-test init itself.
const testing_io_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("tests/testing_io_test.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "zspec", .module = zspec_mod },
},
}),
.test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple },
});

const run_testing_io_tests = b.addRunArtifact(testing_io_tests);

const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
test_step.dependOn(&run_junit_unit_tests.step);
test_step.dependOn(&run_fixture_tests.step);
test_step.dependOn(&run_factory_union_tests.step);
test_step.dependOn(&run_factory_zon_tests.step);
test_step.dependOn(&run_testing_io_tests.step);

const example_step = b.step("example", "Run example tests");
example_step.dependOn(&run_example_tests.step);
example_step.dependOn(&run_factory_union_tests.step);
example_step.dependOn(&run_factory_zon_tests.step);
example_step.dependOn(&run_fixture_tests.step);

// Examples - individual example files
const example_files = [_]struct { name: []const u8, path: []const u8 }{
.{ .name = "examples-basic", .path = "examples/basic_test.zig" },
.{ .name = "examples-hooks", .path = "examples/hooks_test.zig" },
.{ .name = "examples-let", .path = "examples/let_memoization_test.zig" },
.{ .name = "examples-matchers", .path = "examples/matchers_test.zig" },
.{ .name = "examples-factory", .path = "examples/factory_test.zig" },
.{ .name = "examples-factory-zon", .path = "examples/factory_zon_test.zig" },
.{ .name = "examples-fixture", .path = "examples/fixture_test.zig" },
.{ .name = "examples-nested", .path = "examples/nested_contexts_test.zig" },
.{ .name = "examples-ecs", .path = "examples/ecs_integration_test.zig" },
.{ .name = "examples-fsm", .path = "examples/fsm_integration_test.zig" },
};

const examples_all_step = b.step("examples", "Run all examples");

for (example_files) |ex| {
// Integration examples need the optional modules
const needs_integrations = std.mem.indexOf(u8, ex.name, "-ecs") != null or
std.mem.indexOf(u8, ex.name, "-fsm") != null;

const imports = if (needs_integrations) &[_]std.Build.Module.Import{
.{ .name = "zspec", .module = zspec_mod },
.{ .name = "zspec-ecs", .module = zspec_ecs_mod },
.{ .name = "zspec-fsm", .module = zspec_fsm_mod },
} else &[_]std.Build.Module.Import{
.{ .name = "zspec", .module = zspec_mod },
};

const ex_test = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path(ex.path),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = imports,
}),
.test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple },
});

const run_ex = b.addRunArtifact(ex_test);
const ex_step = b.step(ex.name, b.fmt("Run {s}", .{ex.path}));
ex_step.dependOn(&run_ex.step);
examples_all_step.dependOn(&run_ex.step);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Committing the fetched dependency package (zig-pkg/) to the repository is unnecessary and discouraged when using Zig's package manager.

Zig's package manager automatically downloads, verifies, and caches dependencies based on the URL and hash declared in the root build.zig.zon. Committing these files bloats the repository size and can lead to maintenance issues (e.g., out-of-sync dependency code).

Consider removing the zig-pkg/ directory from version control and adding it to your .gitignore file.

.name = .zspec,
.version = "0.9.1",
.fingerprint = 0x940ac7516d8ba28d,
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"tests",
"examples",
},
.dependencies = .{},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Basic ZSpec Example
//!
//! Demonstrates the simplest way to write tests with ZSpec:
//! - Importing and using zspec
//! - Basic test structure
//! - Simple assertions

const std = @import("std");
const zspec = @import("zspec");
const expect = zspec.expect;

// This test block triggers ZSpec to discover all tests in this file
test {
zspec.runAll(@This());
}

// Simple standalone tests (not in a context struct)
test "addition works correctly" {
const result = 2 + 2;
try expect.equal(result, 4);
}

test "strings can be compared" {
const greeting = "hello";
try expect.equal(greeting, "hello");
}

Loading
Loading