Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
9.9 kB · 257 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258// SPDX-FileCopyrightText: © 2024 Jeffrey C. Ollie// SPDX-License-Identifier: GPL-3.0-or-later
//! Integration tests exercising the bindings against a real notmuch database//! built from the embedded corpus in `testing.zig`.
const std = @import("std");
const notmuch = @import("notmuch.zig");const fixture = @import("testing.zig");
test "Database UUIDs are unique per database" { var test_db1 = try fixture.corpusDatabase(); defer test_db1.deinit(); var test_db2 = try fixture.corpusDatabase(); defer test_db2.deinit();
const rev1 = test_db1.database.getRevision(); const rev2 = test_db2.database.getRevision();
try std.testing.expect(!std.mem.eql(u8, rev1.uuid, rev2.uuid)); try std.testing.expectError(error.DatabaseMismatch, rev1.compare(rev2));}
test "Database atomic section error paths" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit(); const db = test_db.database;
// endAtomic without a matching beginAtomic is unbalanced. try std.testing.expectError(error.UnbalancedAtomic, db.endAtomic());
// Atomic sections nest; the extra endAtomic is unbalanced again. try db.beginAtomic(); try db.beginAtomic(); try db.endAtomic(); try db.endAtomic(); try std.testing.expectError(error.UnbalancedAtomic, db.endAtomic());
// On a read-only database notmuch does not open a Xapian transaction, so // a balanced pair still succeeds. try test_db.database.reopen(.read_only); try db.beginAtomic(); try db.endAtomic();}
test "Database message lookup and removal" { const alloc = std.testing.allocator; var test_db = try fixture.corpusDatabase(); defer test_db.deinit(); const db = test_db.database;
try std.testing.expectEqual(@as(?notmuch.Message, null), try db.findMessage("missing@example.org"));
const path = try test_db.path(2); defer alloc.free(path);
const message = try db.findMessageByFilename(path) orelse return error.TestUnexpectedResult; defer message.deinit(); try std.testing.expectEqualStrings("three@example.org", message.getMessageID() orelse "");
try db.removeMessage(path); try std.testing.expectEqual(@as(?notmuch.Message, null), try db.findMessage("three@example.org"));
const query = try db.queryCreate("*"); defer query.deinit(); try std.testing.expectEqual(@as(u32, fixture.corpus.len - 1), try query.countMessages());}
test "Database.reopen switches modes" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit();
// After reopening read-only, writes fail. try test_db.database.reopen(.read_only); const ro_message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; defer ro_message.deinit(); try std.testing.expectError(error.ReadOnlyDatabase, ro_message.addTag("nope"));
// After reopening read-write, writes succeed again. try test_db.database.reopen(.read_write); const rw_message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; defer rw_message.deinit(); try rw_message.addTag("reopened"); var tags = rw_message.getTags(); try std.testing.expect(fixture.hasTag(&tags, "reopened"));}
test "Database.reopen revives a closed database" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit();
try test_db.database.close(); try std.testing.expectError(error.XapianException, test_db.database.findMessage("one@example.org"));
// reopen brings a closed database back into a usable state. try test_db.database.reopen(.read_only); const message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; defer message.deinit(); try std.testing.expectEqualStrings("one@example.org", message.getMessageID() orelse "");}
test "Query tag exclusion error paths" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit();
const query = try test_db.database.queryCreate("tag:spam"); defer query.deinit();
// Excluding a tag that appears explicitly in the query is ignored. try std.testing.expectError(error.Ignored, query.addTagExclude("spam"));
// Other tags can still be excluded. try query.addTagExclude("other");}
test "Query search error paths" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit(); const db = test_db.database;
// Xapian-syntax queries are lenient: a malformed string is treated as // literal terms and still searches successfully. const lenient = try db.queryCreate("tag:(unbalanced"); defer lenient.deinit(); _ = try lenient.countMessages();
// S-expression queries defer parsing until the query runs, so a bad // sexp errors at search/count time. const bad = try db.queryCreateWithSyntax("(unbalanced", .sexp); defer bad.deinit(); try std.testing.expectError(error.BadQuerySyntax, bad.countMessages()); try std.testing.expectError(error.BadQuerySyntax, bad.countThreads()); try std.testing.expectError(error.BadQuerySyntax, bad.searchMessages()); try std.testing.expectError(error.BadQuerySyntax, bad.searchThreads());}
test "Query searches on a closed database" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit();
const query = try test_db.database.queryCreate("*"); defer query.deinit(); try test_db.database.close();
try std.testing.expectError(error.XapianException, query.countMessages()); try std.testing.expectError(error.XapianException, query.searchMessages()); try std.testing.expectError(error.XapianException, query.searchThreads());}
test "Message freeze and thaw error paths" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit();
const message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; defer message.deinit();
// thaw without a matching freeze is unbalanced. try std.testing.expectError(error.UnbalancedFreezeThaw, message.thaw());
// freeze/thaw calls stack; the extra thaw is unbalanced again. try message.freeze(); try message.freeze(); try message.thaw(); try message.thaw(); try std.testing.expectError(error.UnbalancedFreezeThaw, message.thaw());
// freeze requires a read-write database. try test_db.database.reopen(.read_only); const ro_message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; defer ro_message.deinit(); try std.testing.expectError(error.ReadOnlyDatabase, ro_message.freeze());}
test "Message property error paths" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit();
const message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; defer message.deinit();
// Property keys may not contain an '=' character. try std.testing.expectError(error.IllegalArgument, message.addProperty("bad=key", "value")); try std.testing.expectError(error.IllegalArgument, message.removeProperty("bad=key", "value"));
// The remove-all variants require a read-write database. try test_db.database.reopen(.read_only); const ro_message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; defer ro_message.deinit(); try std.testing.expectError(error.ReadOnlyDatabase, ro_message.removeAllProperties("test.key")); try std.testing.expectError(error.ReadOnlyDatabase, ro_message.removeAllPropertiesWithPrefix("test."));}
test "Message maildir flags" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit();
const message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; defer message.deinit();
// The corpus filenames carry no ":2," maildir info part. try std.testing.expect(!try message.hasMaildirFlag(.S)); try std.testing.expect(!try message.hasMaildirFlag(.F)); try message.maildirFlagsToTags();}
test "Database.open error reporting" { const missing = notmuch.Database.open(.read_only, .{ .database_path = "/nonexistent/notmuch-zig-test", .config_path = "", }); switch (missing) { .ok => |db| { db.deinit() catch {}; return error.TestUnexpectedResult; }, .err => |e| { defer e.deinit(); try std.testing.expectEqual(notmuch.Database.OpenError.NoDatabase, e.err); }, }
const relative = notmuch.Database.open(.read_only, .{ .database_path = "relative/path", .config_path = "", }); switch (relative) { .ok => |db| { db.deinit() catch {}; return error.TestUnexpectedResult; }, .err => |e| { defer e.deinit(); try std.testing.expectEqual(notmuch.Database.OpenError.PathError, e.err); }, }}
test "writes to a read-only database return errors" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit(); try test_db.database.reopen(.read_only);
const message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; defer message.deinit();
try std.testing.expectError(error.ReadOnlyDatabase, message.addTag("probe")); try std.testing.expectError(error.ReadOnlyDatabase, message.addProperty("probe.key", "value")); try std.testing.expectError(error.ReadOnlyDatabase, message.removeProperty("probe.key", "value")); try std.testing.expectError(error.ReadOnlyDatabase, message.removeAllProperties(null));}
test "module helpers" { try std.testing.expect(notmuch.builtWith("compact")); try std.testing.expect(!notmuch.builtWith("time-travel")); try std.testing.expect(notmuch.tag_max > 0);}