From 79756893caa450bcbde193794dcfa49eff2413a4 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sun, 30 Aug 2026 00:47:46 -0500 Subject: [PATCH] add method doctests to MessageWriter and expand the wrap doctest filename proves messages are delivered under exactly the generated name, cancel gets its own leaves-nothing-behind example split out of the struct-level test, and wrap now verifies representative mappings plus, exhaustively, that no status other than success maps to success. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_017i7R7ReKJAHGQpitxGXqrb --- src/error.zig | 14 +++++++++++ src/helpers.zig | 64 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/error.zig b/src/error.zig index 59b5ce5..6be36d7 100644 --- a/src/error.zig +++ b/src/error.zig @@ -11,6 +11,7 @@ const std = @import("std"); const c = @import("c"); const status = @import("enums.zig").status; +const Status = @import("enums.zig").Status; pub const Error = error{ /// Syntax error in query. @@ -126,6 +127,19 @@ pub fn wrap(rc: c.notmuch_status_t) Error!void { } test wrap { + // NOTMUCH_STATUS_SUCCESS maps to no error at all. try wrap(c.NOTMUCH_STATUS_SUCCESS); + + // Every other status code maps to its corresponding error. + try std.testing.expectError(error.OutOfMemory, wrap(c.NOTMUCH_STATUS_OUT_OF_MEMORY)); + try std.testing.expectError(error.ReadOnlyDatabase, wrap(c.NOTMUCH_STATUS_READ_ONLY_DATABASE)); try std.testing.expectError(error.BadQuerySyntax, wrap(c.NOTMUCH_STATUS_BAD_QUERY_SYNTAX)); + + // No status other than success maps to success. + inline for (@typeInfo(Status).@"enum".fields) |field| { + const code: Status = @enumFromInt(field.value); + if (code != .success) { + try std.testing.expect(std.meta.isError(wrap(@intCast(field.value)))); + } + } } diff --git a/src/helpers.zig b/src/helpers.zig index cd36098..d1dca2d 100644 --- a/src/helpers.zig +++ b/src/helpers.zig @@ -148,6 +148,40 @@ pub const MessageWriter = struct { return self.name_buf[0..self.name_len]; } + test filename { + const fixture = @import("testing.zig"); + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + var buffer: [256]u8 = undefined; + + // The generated name leads with the delivery timestamp so filenames + // sort by arrival, like those written by notmuch insert. + var mw = try MessageWriter.init(std.testing.io, test_db.database, &buffer, .{}); + try std.testing.expect(std.ascii.isDigit(mw.filename()[0])); + try std.testing.expect(std.mem.indexOf(u8, mw.filename(), ".M") != null); + + var name_buf: [name_capacity]u8 = undefined; + const name = name_buf[0..mw.filename().len]; + @memcpy(name, mw.filename()); + + try mw.writer().writeAll( + \\From: Ivan + \\To: Alice + \\Subject: Delivered under a generated name + \\Date: Sun, 07 Apr 2024 08:00:00 +0000 + \\Message-ID: + \\ + \\Short. + \\ + ); + const message = try mw.finish(null); + defer message.deinit(); + + // The indexed message was delivered under exactly that name. + try std.testing.expectEqualStrings(name, std.fs.path.basename(try message.getFilename())); + } + /// Flush the email, atomically materialize it in the mail store, index /// it into the database, and return the indexed message. The caller owns /// the returned message. The database must be open in read-write mode. @@ -180,6 +214,23 @@ pub const MessageWriter = struct { self.dir.close(io); self.* = undefined; } + + test cancel { + const fixture = @import("testing.zig"); + const io = std.testing.io; + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + var buffer: [256]u8 = undefined; + + // A cancelled message leaves nothing behind at its destination. + var mw = try MessageWriter.init(io, test_db.database, &buffer, .{ .subdir = .new }); + try mw.writer().writeAll("From: partial \n"); + var path_buf: [128]u8 = undefined; + const path = try std.fmt.bufPrint(&path_buf, "mail/new/{s}", .{mw.filename()}); + mw.cancel(); + try std.testing.expectError(error.FileNotFound, test_db.tmp.dir.access(io, path, .{})); + } }; test MessageWriter { @@ -191,19 +242,8 @@ test MessageWriter { const db = test_db.database; var buffer: [256]u8 = undefined; - // A cancelled message leaves nothing behind at its destination. - var cancelled = try MessageWriter.init(io, db, &buffer, .{ .subdir = .new }); - try cancelled.writer().writeAll("From: partial \n"); - var cancelled_path_buf: [128]u8 = undefined; - const cancelled_path = try std.fmt.bufPrint(&cancelled_path_buf, "mail/new/{s}", .{cancelled.filename()}); - cancelled.cancel(); - try std.testing.expectError(error.FileNotFound, test_db.tmp.dir.access(io, cancelled_path, .{})); - - // A finished message is materialized in new/ and indexed. The generated - // name leads with the delivery timestamp so filenames sort by arrival. + // A finished message is materialized in new/ and indexed. var finished = try MessageWriter.init(io, db, &buffer, .{ .subdir = .new }); - try std.testing.expect(std.ascii.isDigit(finished.filename()[0])); - try std.testing.expect(std.mem.indexOf(u8, finished.filename(), ".M") != null); try finished.writer().writeAll( \\From: Grace \\To: Alice -- 2.51.2