Skip to content
13 changes: 9 additions & 4 deletions docs/algorithm/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ file_type = "file"
* perms
* owner
* `state`: entry for this file in the state-file (if it exists). This represents the values of the target file
at the time of the last successful sync (which is the same as the values for `source` at that time.
at the time of the last successful sync (which is the same as the values for `source` at that time).
* mtime
* hash
* type (file, symlink)
Expand Down Expand Up @@ -202,8 +202,12 @@ Verify that the actions can be executed
* Check that the state file can be written
* Check that the source file can be written or created.
* Check that missing parent directories can be created.
* Check that the correct file owner can be set.
(i.e., either `cfgsync` is running as root or as the intended owner of the **config file**)
* Check that the owner of the target file matches the owner configuration for that file.
* Check that the permissions of the target file are a valid value from the target column of
the [permission mapping](./permissions-and-owner.md#permissions) for the configured permissions
(i.e., 600 or 700 if permissions are set to "private"; 644 or 755 if permissions are set to "public" or are
omitted)
* Check that either `cfgsync` is running as root or as the owner of the **config file**
* `DeleteSource`:
* Check that the state file can be written
* Check the source file can be deleted
Expand All @@ -217,7 +221,8 @@ Verify that the actions can be executed
* `Conflict`:
* Check if any of `CopyToTarget` or `CopyToSource` are possible.

All failed checks are immediately logged as warnings. For the next steps, a file is only considered "valid" if EVERY check for that file passes.
All failed checks are immediately logged as warnings. For the next steps, a file is only considered "valid" if EVERY
check for that file passes.

# 5. Execute changes

Expand Down
25 changes: 12 additions & 13 deletions e2e-tests/checked/1-config-edge-cases/absolute-target.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,18 @@ Deno.test("copy data to absolute target path", async (t) => {
],
}));

await testbed.run({ args: ["--config", "config.toml", "sync"] });
await testbed.testSync("config.toml", {
code: 0,
stdout: deindent`
copied .subdir/subsub/new.txt -> target

source -> target: 1
target -> source: 0
deleted target: 0
deleted source: 0
`,
stderr: "",
});

await testbed.assertTestDir([
`user:user | 644 | 0 | config.cfgsync.state | ${STATE_FILE}`,
Expand All @@ -32,16 +43,4 @@ Deno.test("copy data to absolute target path", async (t) => {
"user:user | 755 | 0 | target/.subdir/subsub/",
"user:user | 644 | 0 | target/.subdir/subsub/new.txt | newfile",
]);
testbed.assertOutput({
code: 0,
stdout: deindent`
copied .subdir/subsub/new.txt -> target

source -> target: 1
target -> source: 0
deleted target: 0
deleted source: 0
`,
stderr: "",
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,19 @@ Deno.test("ignore-non-matching", async (t) => {
],
});

await testbed.run({ args: ["--config", "config.toml", "sync"] });
await testbed.testSync("config.toml", {
code: 0,
stdout: deindent`
copied file.txt -> target
copied target -> unmatched-target.txt

source -> target: 1
target -> source: 1
deleted target: 0
deleted source: 0
`,
stderr: "",
});

await testbed.assertTestDir([
`user:user | 644 | 0 | config.cfgsync.state | ${STATE_FILE}`,
Expand All @@ -33,17 +45,4 @@ Deno.test("ignore-non-matching", async (t) => {
"user:user | 644 | 0 | target/not-matched.conf | should not be synced",
"user:user | 644 | 0 | target/unmatched-target.txt | target file content",
]);
testbed.assertOutput({
code: 0,
stdout: deindent`
copied file.txt -> target
copied target -> unmatched-target.txt

source -> target: 1
target -> source: 1
deleted target: 0
deleted source: 0
`,
stderr: "",
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,52 +26,38 @@ Deno.test("multi-group-independent", async (t) => {
});

// Status and diff
await testbed.run({ args: ["--config", "config.toml", "status", "--short"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
await testbed.testStatus("config.toml", {
short: deindent`
2→
`,
stderr: "",
});

await testbed.run({ args: ["--config", "config.toml", "status"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
normal: deindent`
source -> target: 2
target -> source: 0
`,
stderr: "",
});

await testbed.run({ args: ["--config", "config.toml", "diff"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
=== file.txt (source -> target) ===
--- ${testDir}/source-a/file.txt${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ ${testDir}/target-a/file.txt${"\t"}
@@ -1 +1 @@
-content from group a
\ No newline at end of file
+(file missing)
\ No newline at end of file

=== file.conf (source -> target) ===
--- ${testDir}/source-b/file.conf${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ ${testDir}/target-b/file.conf${"\t"}
@@ -1 +1 @@
-content from group b
\ No newline at end of file
+(file missing)
\ No newline at end of file`,
stderr: "",
});
await testbed.testDiff("config.toml", deindent`
=== file.txt (source -> target) ===
--- ${testDir}/source-a/file.txt${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ ${testDir}/target-a/file.txt${"\t"}
@@ -1 +1 @@
-content from group a
\ No newline at end of file
+(file missing)
\ No newline at end of file

=== file.conf (source -> target) ===
--- ${testDir}/source-b/file.conf${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ ${testDir}/target-b/file.conf${"\t"}
@@ -1 +1 @@
-content from group b
\ No newline at end of file
+(file missing)
\ No newline at end of file`,
);

// First sync: both groups copy to target
await testbed.run({ args: ["--config", "config.toml", "sync"] });
testbed.assertOutput({
await testbed.testSync("config.toml", {
code: 0,
stdout: deindent`
copied file.txt -> target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,46 +19,31 @@ Deno.test("relative-paths", async (t) => {
});

// Status shows pending copy before sync
await testbed.run({ args: ["--config", "subdir/config.toml", "status"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
await testbed.testStatus("subdir/config.toml", {
short: deindent`
1→
`,
normal: deindent`
source -> target: 1
target -> source: 0
`,
stderr: "",
});

await testbed.run({ args: ["--config", "subdir/config.toml", "status", "--short"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
1→
`,
stderr: "",
});

// Diff shows pending change with relative paths resolved absolutely.
// The target file doesn't exist yet, so its mtime is empty (trailing tab).
await testbed.run({ args: ["--config", "subdir/config.toml", "diff"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
=== same.txt (source -> target) ===
--- ${testDir}/subdir/source/same.txt${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ ${testDir}/target/same.txt
@@ -1 +1 @@
-identical content
\ No newline at end of file
+(file missing)
\ No newline at end of file
`,
stderr: "",
});
await testbed.testDiff("subdir/config.toml", deindent`
=== same.txt (source -> target) ===
--- ${testDir}/subdir/source/same.txt${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ ${testDir}/target/same.txt
@@ -1 +1 @@
-identical content
\ No newline at end of file
+(file missing)
\ No newline at end of file
`);

// Sync copies the file
await testbed.run({ args: ["--config", "subdir/config.toml", "sync"] });
testbed.assertOutput({
await testbed.testSync("subdir/config.toml", {
code: 0,
stdout: deindent`
copied same.txt -> target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,53 +38,37 @@ Deno.test({ name: "resolve tilde in target path", ignore: runningOutsideDocker }
});

// Status shows both copies before sync
await testbed.run({ args: ["--config", "config.toml", "status", "--short"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
await testbed.testStatus("config.toml", {
short: deindent`
1→ 1←
`,
stderr: "",
});

await testbed.run({ args: ["--config", "config.toml", "status"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
normal: deindent`
source -> target: 1
target -> source: 1
`,
stderr: "",
});

// Diff with tilde-expanded target paths
await testbed.run({ args: ["--config", "config.toml", "diff"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
=== cfgsync-test-subdir/data-source.txt (source -> target) ===
--- ${testDir}/source/cfgsync-test-subdir/data-source.txt${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ /home/user/cfgsync-test-subdir/data-source.txt
@@ -1 +1 @@
-My data
\ No newline at end of file
+(file missing)
\ No newline at end of file
=== cfgsync-test-subdir/subdir/data-home.txt (target -> source) ===
--- /home/user/cfgsync-test-subdir/subdir/data-home.txt${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ ${testDir}/source/cfgsync-test-subdir/subdir/data-home.txt
@@ -1 +1 @@
-My data
\ No newline at end of file
+(file missing)
\ No newline at end of file
`,
stderr: "",
});

await testbed.run({ args: ["--config", "config.toml", "sync"] });
await testbed.testDiff("config.toml", deindent`
=== cfgsync-test-subdir/data-source.txt (source -> target) ===
--- ${testDir}/source/cfgsync-test-subdir/data-source.txt${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ /home/user/cfgsync-test-subdir/data-source.txt
@@ -1 +1 @@
-My data
\ No newline at end of file
+(file missing)
\ No newline at end of file
=== cfgsync-test-subdir/subdir/data-home.txt (target -> source) ===
--- /home/user/cfgsync-test-subdir/subdir/data-home.txt${"\t"}2020-01-01 00:00:00.000000000 +0000
+++ ${testDir}/source/cfgsync-test-subdir/subdir/data-home.txt
@@ -1 +1 @@
-My data
\ No newline at end of file
+(file missing)
\ No newline at end of file
`);

testbed.assertOutput({
await testbed.testSync("config.toml", {
code: 0,
stdout: deindent`
copied cfgsync-test-subdir/data-source.txt -> target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,24 @@ Deno.test("new-file-update-state", async (t) => {
});

// Test short status
await testbed.run({ args: ["--config", "config.toml", "status", "--short"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
await testbed.testStatus("config.toml", {
short: deindent`
↺1
`,
stderr: "",
});

// status: both sides have same content and no state entry → UpdateState
await testbed.run({ args: ["--config", "config.toml", "status"] });
testbed.assertOutput({
code: 0,
stdout: deindent`
normal: deindent`
source -> target: 0
target -> source: 0
state update: 1
`,
stderr: "",
});

// TODO: Test short status (new icon to show that everything is fine, but sync should be run anyway)

// diff: UpdateState produces no output
await testbed.run({ args: ["--config", "config.toml", "diff"] });
testbed.assertOutput({
code: 0,
stdout: "",
stderr: "",
});
await testbed.testDiff("config.toml", "");

// sync: no files copied, state file created with hash for tracking
await testbed.run({ args: ["--config", "config.toml", "sync"] });
testbed.assertOutput({
await testbed.testSync("config.toml", {
code: 0,
stdout: deindent`
source -> target: 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ Deno.test("sync-symlink-forward", async (t) => {
],
});

await testbed.run({ args: ["--config", "config.toml", "sync"] });
testbed.assertOutput({
await testbed.testSync("config.toml", {
code: 0,
stdout: deindent`
copied link.txt -> target
Expand Down
Loading