pushwork 1.0.4 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -328
- package/dist/.pushwork/automerge/3P/Dm3ekE2pmjGnWvDaG3vSR7ww98/snapshot/aa2349c94955ea561f698720142f9d884a6872d9f82dc332d578c216beb0df0e +0 -0
- package/dist/.pushwork/automerge/st/orage-adapter-id +1 -0
- package/dist/.pushwork/config.json +15 -0
- package/dist/.pushwork/snapshot.json +7 -0
- package/dist/cli.js +231 -170
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +51 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +799 -0
- package/dist/commands.js.map +1 -0
- package/dist/core/change-detection.d.ts +6 -19
- package/dist/core/change-detection.d.ts.map +1 -1
- package/dist/core/change-detection.js +101 -80
- package/dist/core/change-detection.js.map +1 -1
- package/dist/{config/index.d.ts → core/config.d.ts} +13 -3
- package/dist/core/config.d.ts.map +1 -0
- package/dist/{config/index.js → core/config.js} +55 -73
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/move-detection.d.ts +12 -50
- package/dist/core/move-detection.d.ts.map +1 -1
- package/dist/core/move-detection.js +58 -139
- package/dist/core/move-detection.js.map +1 -1
- package/dist/core/snapshot.d.ts +0 -4
- package/dist/core/snapshot.d.ts.map +1 -1
- package/dist/core/snapshot.js +2 -11
- package/dist/core/snapshot.js.map +1 -1
- package/dist/core/sync-engine.d.ts +5 -11
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +220 -362
- package/dist/core/sync-engine.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -6
- package/dist/index.js.map +1 -1
- package/dist/types/config.d.ts +43 -67
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/documents.d.ts +15 -3
- package/dist/types/documents.d.ts.map +1 -1
- package/dist/types/documents.js.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/snapshot.d.ts +3 -21
- package/dist/types/snapshot.d.ts.map +1 -1
- package/dist/types/snapshot.js +0 -14
- package/dist/types/snapshot.js.map +1 -1
- package/dist/utils/content.d.ts.map +1 -1
- package/dist/utils/content.js +2 -6
- package/dist/utils/content.js.map +1 -1
- package/dist/utils/directory.d.ts +10 -0
- package/dist/utils/directory.d.ts.map +1 -0
- package/dist/utils/directory.js +37 -0
- package/dist/utils/directory.js.map +1 -0
- package/dist/utils/fs.d.ts +15 -2
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +63 -53
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/mime-types.d.ts.map +1 -1
- package/dist/utils/mime-types.js +11 -4
- package/dist/utils/mime-types.js.map +1 -1
- package/dist/utils/network-sync.d.ts +0 -6
- package/dist/utils/network-sync.d.ts.map +1 -1
- package/dist/utils/network-sync.js +55 -99
- package/dist/utils/network-sync.js.map +1 -1
- package/dist/utils/output.d.ts +129 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +375 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/repo-factory.d.ts +2 -6
- package/dist/utils/repo-factory.d.ts.map +1 -1
- package/dist/utils/repo-factory.js +8 -22
- package/dist/utils/repo-factory.js.map +1 -1
- package/dist/utils/string-similarity.d.ts +14 -0
- package/dist/utils/string-similarity.d.ts.map +1 -0
- package/dist/utils/string-similarity.js +43 -0
- package/dist/utils/string-similarity.js.map +1 -0
- package/dist/utils/trace.d.ts +19 -0
- package/dist/utils/trace.d.ts.map +1 -0
- package/dist/utils/trace.js +68 -0
- package/dist/utils/trace.js.map +1 -0
- package/package.json +17 -12
- package/src/cli.ts +326 -252
- package/src/commands.ts +988 -0
- package/src/core/change-detection.ts +199 -162
- package/src/{config/index.ts → core/config.ts} +65 -82
- package/src/core/index.ts +1 -1
- package/src/core/move-detection.ts +74 -180
- package/src/core/snapshot.ts +2 -12
- package/src/core/sync-engine.ts +248 -499
- package/src/index.ts +0 -10
- package/src/types/config.ts +50 -72
- package/src/types/documents.ts +16 -3
- package/src/types/index.ts +0 -5
- package/src/types/snapshot.ts +1 -23
- package/src/utils/content.ts +2 -6
- package/src/utils/directory.ts +50 -0
- package/src/utils/fs.ts +67 -56
- package/src/utils/index.ts +1 -6
- package/src/utils/mime-types.ts +12 -4
- package/src/utils/network-sync.ts +79 -137
- package/src/utils/output.ts +450 -0
- package/src/utils/repo-factory.ts +13 -31
- package/src/utils/string-similarity.ts +54 -0
- package/src/utils/trace.ts +70 -0
- package/test/integration/exclude-patterns.test.ts +6 -15
- package/test/integration/fuzzer.test.ts +308 -391
- package/test/integration/init-sync.test.ts +89 -0
- package/test/integration/sync-deletion.test.ts +2 -61
- package/test/integration/sync-flow.test.ts +4 -24
- package/test/jest.setup.ts +34 -0
- package/test/unit/deletion-behavior.test.ts +3 -14
- package/test/unit/enhanced-mime-detection.test.ts +0 -22
- package/test/unit/snapshot.test.ts +2 -29
- package/test/unit/sync-convergence.test.ts +3 -198
- package/test/unit/sync-timing.test.ts +0 -44
- package/test/unit/utils.test.ts +0 -2
- package/tsconfig.json +3 -3
- package/dist/browser/browser-sync-engine.d.ts +0 -64
- package/dist/browser/browser-sync-engine.d.ts.map +0 -1
- package/dist/browser/browser-sync-engine.js +0 -303
- package/dist/browser/browser-sync-engine.js.map +0 -1
- package/dist/browser/filesystem-adapter.d.ts +0 -84
- package/dist/browser/filesystem-adapter.d.ts.map +0 -1
- package/dist/browser/filesystem-adapter.js +0 -413
- package/dist/browser/filesystem-adapter.js.map +0 -1
- package/dist/browser/index.d.ts +0 -36
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js +0 -90
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/types.d.ts +0 -70
- package/dist/browser/types.d.ts.map +0 -1
- package/dist/browser/types.js +0 -6
- package/dist/browser/types.js.map +0 -1
- package/dist/cli/commands.d.ts +0 -77
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -904
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -19
- package/dist/cli/index.js.map +0 -1
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js.map +0 -1
- package/dist/core/isomorphic-snapshot.d.ts +0 -58
- package/dist/core/isomorphic-snapshot.d.ts.map +0 -1
- package/dist/core/isomorphic-snapshot.js +0 -204
- package/dist/core/isomorphic-snapshot.js.map +0 -1
- package/dist/platform/browser-filesystem.d.ts +0 -26
- package/dist/platform/browser-filesystem.d.ts.map +0 -1
- package/dist/platform/browser-filesystem.js +0 -91
- package/dist/platform/browser-filesystem.js.map +0 -1
- package/dist/platform/filesystem.d.ts +0 -29
- package/dist/platform/filesystem.d.ts.map +0 -1
- package/dist/platform/filesystem.js +0 -65
- package/dist/platform/filesystem.js.map +0 -1
- package/dist/platform/node-filesystem.d.ts +0 -21
- package/dist/platform/node-filesystem.d.ts.map +0 -1
- package/dist/platform/node-filesystem.js +0 -93
- package/dist/platform/node-filesystem.js.map +0 -1
- package/dist/utils/content-similarity.d.ts +0 -53
- package/dist/utils/content-similarity.d.ts.map +0 -1
- package/dist/utils/content-similarity.js +0 -155
- package/dist/utils/content-similarity.js.map +0 -1
- package/dist/utils/fs-browser.d.ts +0 -57
- package/dist/utils/fs-browser.d.ts.map +0 -1
- package/dist/utils/fs-browser.js +0 -311
- package/dist/utils/fs-browser.js.map +0 -1
- package/dist/utils/fs-node.d.ts +0 -53
- package/dist/utils/fs-node.d.ts.map +0 -1
- package/dist/utils/fs-node.js +0 -220
- package/dist/utils/fs-node.js.map +0 -1
- package/dist/utils/isomorphic.d.ts +0 -29
- package/dist/utils/isomorphic.d.ts.map +0 -1
- package/dist/utils/isomorphic.js +0 -139
- package/dist/utils/isomorphic.js.map +0 -1
- package/dist/utils/pure.d.ts +0 -25
- package/dist/utils/pure.d.ts.map +0 -1
- package/dist/utils/pure.js +0 -112
- package/dist/utils/pure.js.map +0 -1
- package/src/cli/commands.ts +0 -1207
- package/src/cli/index.ts +0 -2
- package/src/utils/content-similarity.ts +0 -194
- package/test/README-TESTING-GAPS.md +0 -174
- package/test/unit/content-similarity.test.ts +0 -236
|
@@ -119,47 +119,32 @@ describe("Pushwork Fuzzer", () => {
|
|
|
119
119
|
await fs.mkdir(repoA);
|
|
120
120
|
await fs.mkdir(repoB);
|
|
121
121
|
|
|
122
|
-
console.log(`Test directories created:`);
|
|
123
|
-
console.log(` Repo A: ${repoA}`);
|
|
124
|
-
console.log(` Repo B: ${repoB}`);
|
|
125
|
-
|
|
126
122
|
// Step 1: Create a file in repo A
|
|
127
123
|
const testFile = path.join(repoA, "test.txt");
|
|
128
124
|
await fs.writeFile(testFile, "Hello, Pushwork!");
|
|
129
|
-
console.log(`Created test file: ${testFile}`);
|
|
130
125
|
|
|
131
126
|
// Step 2: Initialize repo A
|
|
132
|
-
console.log(`Initializing repo A...`);
|
|
133
127
|
await pushwork(["init", "."], repoA);
|
|
134
|
-
console.log(`Repo A initialized successfully`);
|
|
135
128
|
|
|
136
129
|
// Wait a moment for initialization to complete
|
|
137
130
|
await wait(1000);
|
|
138
131
|
|
|
139
132
|
// Step 3: Get the root URL from repo A
|
|
140
|
-
console.log(`Getting root URL from repo A...`);
|
|
141
133
|
const { stdout: rootUrl } = await pushwork(["url"], repoA);
|
|
142
134
|
const cleanRootUrl = rootUrl.trim();
|
|
143
|
-
console.log(`Root URL: ${cleanRootUrl}`);
|
|
144
135
|
|
|
145
136
|
expect(cleanRootUrl).toMatch(/^automerge:/);
|
|
146
137
|
|
|
147
138
|
// Step 4: Clone repo A to repo B
|
|
148
|
-
console.log(`Cloning repo A to repo B...`);
|
|
149
139
|
await pushwork(["clone", cleanRootUrl, repoB], tmpDir);
|
|
150
|
-
console.log(`Repo B cloned successfully`);
|
|
151
140
|
|
|
152
141
|
// Wait a moment for clone to complete
|
|
153
142
|
await wait(1000);
|
|
154
143
|
|
|
155
144
|
// Step 5: Verify both repos have the same content
|
|
156
|
-
console.log(`Computing hashes...`);
|
|
157
145
|
const hashA = await hashDirectory(repoA);
|
|
158
146
|
const hashB = await hashDirectory(repoB);
|
|
159
147
|
|
|
160
|
-
console.log(`Hash A: ${hashA}`);
|
|
161
|
-
console.log(`Hash B: ${hashB}`);
|
|
162
|
-
|
|
163
148
|
expect(hashA).toBe(hashB);
|
|
164
149
|
|
|
165
150
|
// Step 6: Verify the file exists in both repos
|
|
@@ -176,325 +161,333 @@ describe("Pushwork Fuzzer", () => {
|
|
|
176
161
|
expect(contentA).toBe("Hello, Pushwork!");
|
|
177
162
|
expect(contentB).toBe("Hello, Pushwork!");
|
|
178
163
|
expect(contentA).toBe(contentB);
|
|
179
|
-
|
|
180
|
-
console.log(`✅ Test passed! Both repos are identical.`);
|
|
181
|
-
}, 30000); // 30 second timeout for this test
|
|
164
|
+
}, 10000); // 10 second timeout for this test
|
|
182
165
|
});
|
|
183
166
|
|
|
184
167
|
describe("Manual Fuzzing Tests", () => {
|
|
185
|
-
it(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
await wait(500);
|
|
230
|
-
|
|
231
|
-
// Clone to B
|
|
232
|
-
const { stdout: rootUrl } = await pushwork(["url"], repoA);
|
|
233
|
-
await pushwork(["clone", rootUrl.trim(), repoB], tmpDir);
|
|
234
|
-
await wait(500);
|
|
235
|
-
|
|
236
|
-
// Edit AND rename file on A (the suspicious operation!)
|
|
237
|
-
await fs.writeFile(path.join(repoA, "original.txt"), "edited content");
|
|
238
|
-
await fs.rename(
|
|
239
|
-
path.join(repoA, "original.txt"),
|
|
240
|
-
path.join(repoA, "renamed.txt")
|
|
241
|
-
);
|
|
168
|
+
it.concurrent(
|
|
169
|
+
"should handle a simple edit on one side",
|
|
170
|
+
async () => {
|
|
171
|
+
const tmpObj = tmp.dirSync({ unsafeCleanup: true });
|
|
172
|
+
const testRoot = path.join(
|
|
173
|
+
tmpObj.name,
|
|
174
|
+
`test-manual-a-${Date.now()}-${Math.random()}`
|
|
175
|
+
);
|
|
176
|
+
await fs.mkdir(testRoot, { recursive: true });
|
|
177
|
+
const repoA = path.join(testRoot, "manual-a");
|
|
178
|
+
const repoB = path.join(testRoot, "manual-b");
|
|
179
|
+
await fs.mkdir(repoA);
|
|
180
|
+
await fs.mkdir(repoB);
|
|
181
|
+
|
|
182
|
+
// Initialize repo A with a file
|
|
183
|
+
await fs.writeFile(path.join(repoA, "test.txt"), "initial content");
|
|
184
|
+
await pushwork(["init", "."], repoA);
|
|
185
|
+
await wait(500);
|
|
186
|
+
|
|
187
|
+
// Clone to B
|
|
188
|
+
const { stdout: rootUrl } = await pushwork(["url"], repoA);
|
|
189
|
+
await pushwork(["clone", rootUrl.trim(), repoB], tmpDir);
|
|
190
|
+
await wait(500);
|
|
191
|
+
|
|
192
|
+
// Edit file on A
|
|
193
|
+
await fs.writeFile(path.join(repoA, "test.txt"), "modified content");
|
|
194
|
+
|
|
195
|
+
// Sync A
|
|
196
|
+
await pushwork(["sync"], repoA);
|
|
197
|
+
await wait(1000);
|
|
198
|
+
|
|
199
|
+
// Sync B to pull changes
|
|
200
|
+
await pushwork(["sync"], repoB);
|
|
201
|
+
await wait(1000);
|
|
202
|
+
|
|
203
|
+
// Verify they match
|
|
204
|
+
const contentA = await fs.readFile(
|
|
205
|
+
path.join(repoA, "test.txt"),
|
|
206
|
+
"utf-8"
|
|
207
|
+
);
|
|
208
|
+
const contentB = await fs.readFile(
|
|
209
|
+
path.join(repoB, "test.txt"),
|
|
210
|
+
"utf-8"
|
|
211
|
+
);
|
|
242
212
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
await wait(1000);
|
|
246
|
-
await pushwork(["sync"], repoB);
|
|
247
|
-
await wait(1000);
|
|
213
|
+
expect(contentA).toBe("modified content");
|
|
214
|
+
expect(contentB).toBe("modified content");
|
|
248
215
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
216
|
+
// Cleanup
|
|
217
|
+
tmpObj.removeCallback();
|
|
218
|
+
},
|
|
219
|
+
30000
|
|
220
|
+
);
|
|
254
221
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
222
|
+
it.concurrent(
|
|
223
|
+
"should handle edit + rename on one side",
|
|
224
|
+
async () => {
|
|
225
|
+
const tmpObj = tmp.dirSync({ unsafeCleanup: true });
|
|
226
|
+
const testRoot = path.join(
|
|
227
|
+
tmpObj.name,
|
|
228
|
+
`test-rename-${Date.now()}-${Math.random()}`
|
|
229
|
+
);
|
|
230
|
+
await fs.mkdir(testRoot, { recursive: true });
|
|
231
|
+
const repoA = path.join(testRoot, "rename-a");
|
|
232
|
+
const repoB = path.join(testRoot, "rename-b");
|
|
233
|
+
await fs.mkdir(repoA);
|
|
234
|
+
await fs.mkdir(repoB);
|
|
235
|
+
|
|
236
|
+
// Initialize repo A with a file
|
|
237
|
+
await fs.writeFile(
|
|
238
|
+
path.join(repoA, "original.txt"),
|
|
239
|
+
"original content"
|
|
240
|
+
);
|
|
241
|
+
await pushwork(["init", "."], repoA);
|
|
242
|
+
await wait(500);
|
|
243
|
+
|
|
244
|
+
// Clone to B
|
|
245
|
+
const { stdout: rootUrl } = await pushwork(["url"], repoA);
|
|
246
|
+
await pushwork(["clone", rootUrl.trim(), repoB], tmpDir);
|
|
247
|
+
await wait(500);
|
|
248
|
+
|
|
249
|
+
// Edit AND rename file on A (the suspicious operation!)
|
|
250
|
+
await fs.writeFile(path.join(repoA, "original.txt"), "edited content");
|
|
251
|
+
await fs.rename(
|
|
252
|
+
path.join(repoA, "original.txt"),
|
|
253
|
+
path.join(repoA, "renamed.txt")
|
|
254
|
+
);
|
|
264
255
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
256
|
+
// Sync both sides
|
|
257
|
+
await pushwork(["sync"], repoA);
|
|
258
|
+
await wait(1000);
|
|
259
|
+
await pushwork(["sync"], repoB);
|
|
260
|
+
await wait(1000);
|
|
261
|
+
|
|
262
|
+
// One more round for convergence
|
|
263
|
+
await pushwork(["sync"], repoA);
|
|
264
|
+
await wait(1000);
|
|
265
|
+
await pushwork(["sync"], repoB);
|
|
266
|
+
await wait(1000);
|
|
267
|
+
|
|
268
|
+
// Verify: original.txt should not exist, renamed.txt should exist with edited content
|
|
269
|
+
const originalExistsA = await pathExists(
|
|
270
|
+
path.join(repoA, "original.txt")
|
|
271
|
+
);
|
|
272
|
+
const originalExistsB = await pathExists(
|
|
273
|
+
path.join(repoB, "original.txt")
|
|
274
|
+
);
|
|
275
|
+
const renamedExistsA = await pathExists(
|
|
276
|
+
path.join(repoA, "renamed.txt")
|
|
277
|
+
);
|
|
278
|
+
const renamedExistsB = await pathExists(
|
|
279
|
+
path.join(repoB, "renamed.txt")
|
|
280
|
+
);
|
|
269
281
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const contentB = await fs.readFile(
|
|
275
|
-
path.join(repoB, "renamed.txt"),
|
|
276
|
-
"utf-8"
|
|
277
|
-
);
|
|
282
|
+
expect(originalExistsA).toBe(false);
|
|
283
|
+
expect(originalExistsB).toBe(false);
|
|
284
|
+
expect(renamedExistsA).toBe(true);
|
|
285
|
+
expect(renamedExistsB).toBe(true);
|
|
278
286
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
287
|
+
const contentA = await fs.readFile(
|
|
288
|
+
path.join(repoA, "renamed.txt"),
|
|
289
|
+
"utf-8"
|
|
290
|
+
);
|
|
291
|
+
const contentB = await fs.readFile(
|
|
292
|
+
path.join(repoB, "renamed.txt"),
|
|
293
|
+
"utf-8"
|
|
294
|
+
);
|
|
282
295
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
296
|
+
expect(contentA).toBe("edited content");
|
|
297
|
+
expect(contentB).toBe("edited content");
|
|
298
|
+
|
|
299
|
+
// Cleanup
|
|
300
|
+
tmpObj.removeCallback();
|
|
301
|
+
},
|
|
302
|
+
120000
|
|
303
|
+
); // 2 minute timeout
|
|
304
|
+
|
|
305
|
+
it.concurrent(
|
|
306
|
+
"should handle simplest case: clone then add file",
|
|
307
|
+
async () => {
|
|
308
|
+
const tmpObj = tmp.dirSync({ unsafeCleanup: true });
|
|
309
|
+
const testRoot = path.join(
|
|
310
|
+
tmpObj.name,
|
|
311
|
+
`test-simple-${Date.now()}-${Math.random()}`
|
|
312
|
+
);
|
|
313
|
+
await fs.mkdir(testRoot, { recursive: true });
|
|
314
|
+
const repoA = path.join(testRoot, "simple-a");
|
|
315
|
+
const repoB = path.join(testRoot, "simple-b");
|
|
316
|
+
await fs.mkdir(repoA);
|
|
317
|
+
await fs.mkdir(repoB);
|
|
318
|
+
|
|
319
|
+
// Initialize repo A
|
|
320
|
+
await fs.writeFile(path.join(repoA, "initial.txt"), "initial");
|
|
321
|
+
await pushwork(["init", "."], repoA);
|
|
322
|
+
await wait(1000);
|
|
323
|
+
|
|
324
|
+
// Clone to B
|
|
325
|
+
const { stdout: rootUrl } = await pushwork(["url"], repoA);
|
|
326
|
+
await pushwork(["clone", rootUrl.trim(), repoB], tmpDir);
|
|
327
|
+
await wait(1000);
|
|
328
|
+
|
|
329
|
+
// B: Create a new file (nothing else happens)
|
|
330
|
+
await fs.writeFile(path.join(repoB, "aaa.txt"), "");
|
|
331
|
+
|
|
332
|
+
// B syncs
|
|
333
|
+
await pushwork(["sync"], repoB);
|
|
334
|
+
await wait(1000);
|
|
335
|
+
|
|
336
|
+
// A syncs
|
|
337
|
+
await pushwork(["sync"], repoA);
|
|
338
|
+
await wait(1000);
|
|
339
|
+
|
|
340
|
+
// Check convergence
|
|
341
|
+
const filesA = await fs.readdir(repoA);
|
|
342
|
+
const filesB = await fs.readdir(repoB);
|
|
343
|
+
const filteredFilesA = filesA.filter((f) => !f.startsWith("."));
|
|
344
|
+
const filteredFilesB = filesB.filter((f) => !f.startsWith("."));
|
|
345
|
+
expect(filteredFilesA).toEqual(filteredFilesB);
|
|
346
|
+
|
|
347
|
+
expect(await pathExists(path.join(repoA, "aaa.txt"))).toBe(true);
|
|
348
|
+
expect(await pathExists(path.join(repoB, "aaa.txt"))).toBe(true);
|
|
349
|
+
|
|
350
|
+
// Cleanup
|
|
351
|
+
tmpObj.removeCallback();
|
|
352
|
+
},
|
|
353
|
+
20000
|
|
354
|
+
);
|
|
288
355
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
356
|
+
it.concurrent(
|
|
357
|
+
"should handle minimal shrunk case: editAndRename non-existent + add same file",
|
|
358
|
+
async () => {
|
|
359
|
+
const tmpObj = tmp.dirSync({ unsafeCleanup: true });
|
|
360
|
+
const testRoot = path.join(
|
|
361
|
+
tmpObj.name,
|
|
362
|
+
`test-shrunk-${Date.now()}-${Math.random()}`
|
|
363
|
+
);
|
|
364
|
+
await fs.mkdir(testRoot, { recursive: true });
|
|
365
|
+
const repoA = path.join(testRoot, "shrunk-a");
|
|
366
|
+
const repoB = path.join(testRoot, "shrunk-b");
|
|
367
|
+
await fs.mkdir(repoA);
|
|
368
|
+
await fs.mkdir(repoB);
|
|
369
|
+
|
|
370
|
+
// Initialize repo A
|
|
371
|
+
await fs.writeFile(path.join(repoA, "initial.txt"), "initial");
|
|
372
|
+
await pushwork(["init", "."], repoA);
|
|
373
|
+
await wait(1000); // Match manual test timing
|
|
374
|
+
|
|
375
|
+
// Clone to B
|
|
376
|
+
const { stdout: rootUrl } = await pushwork(["url"], repoA);
|
|
377
|
+
await pushwork(["clone", rootUrl.trim(), repoB], tmpDir);
|
|
378
|
+
await wait(1000); // Match manual test timing
|
|
379
|
+
|
|
380
|
+
// A: Try to editAndRename a non-existent file (this is from the shrunk test case)
|
|
381
|
+
// This operation should be a no-op since aaa.txt doesn't exist
|
|
382
|
+
const fromPath = path.join(repoA, "aaa.txt");
|
|
383
|
+
const toPath = path.join(repoA, "aa/aa/aaa.txt");
|
|
384
|
+
if ((await pathExists(fromPath)) && !(await pathExists(toPath))) {
|
|
385
|
+
await fs.writeFile(fromPath, "");
|
|
386
|
+
await fs.mkdir(path.dirname(toPath), { recursive: true });
|
|
387
|
+
await fs.rename(fromPath, toPath);
|
|
388
|
+
}
|
|
293
389
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
await pushwork(["clone", rootUrl.trim(), repoB], tmpDir);
|
|
297
|
-
await wait(1000);
|
|
390
|
+
// B: Create the same file that A tried to operate on
|
|
391
|
+
await fs.writeFile(path.join(repoB, "aaa.txt"), "");
|
|
298
392
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
393
|
+
// Sync multiple rounds (use 1s waits for reliable network propagation)
|
|
394
|
+
// Pattern: A, B, A (like manual test that worked)
|
|
395
|
+
await pushwork(["sync"], repoA);
|
|
396
|
+
await wait(1000);
|
|
302
397
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const syncB = await pushwork(["sync"], repoB);
|
|
306
|
-
console.log("B pushed aaa.txt?", syncB.stdout.includes("aaa.txt"));
|
|
307
|
-
console.log("B full output:\n", syncB.stdout);
|
|
308
|
-
await wait(1000);
|
|
398
|
+
// Check what B sees before sync
|
|
399
|
+
await pushwork(["diff", "--name-only"], repoB);
|
|
309
400
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const syncA = await pushwork(["sync"], repoA);
|
|
313
|
-
console.log("A pulled aaa.txt?", syncA.stdout.includes("aaa.txt"));
|
|
314
|
-
await wait(1000);
|
|
401
|
+
await pushwork(["sync"], repoB);
|
|
402
|
+
await wait(1000);
|
|
315
403
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const filesB = await fs.readdir(repoB);
|
|
319
|
-
console.log(
|
|
320
|
-
"Files in A:",
|
|
321
|
-
filesA.filter((f) => !f.startsWith("."))
|
|
322
|
-
);
|
|
323
|
-
console.log(
|
|
324
|
-
"Files in B:",
|
|
325
|
-
filesB.filter((f) => !f.startsWith("."))
|
|
326
|
-
);
|
|
404
|
+
await pushwork(["sync"], repoA);
|
|
405
|
+
await wait(1000);
|
|
327
406
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
407
|
+
// Debug: Check what files exist
|
|
408
|
+
const filesA = await fs.readdir(repoA);
|
|
409
|
+
const filesB = await fs.readdir(repoB);
|
|
410
|
+
const filteredFilesA = filesA.filter((f) => !f.startsWith("."));
|
|
411
|
+
const filteredFilesB = filesB.filter((f) => !f.startsWith("."));
|
|
412
|
+
expect(filteredFilesA).toEqual(filteredFilesB);
|
|
331
413
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
await fs.mkdir(repoA);
|
|
336
|
-
await fs.mkdir(repoB);
|
|
414
|
+
// Verify convergence
|
|
415
|
+
const hashA = await hashDirectory(repoA);
|
|
416
|
+
const hashB = await hashDirectory(repoB);
|
|
337
417
|
|
|
338
|
-
|
|
339
|
-
await fs.writeFile(path.join(repoA, "initial.txt"), "initial");
|
|
340
|
-
await pushwork(["init", "."], repoA);
|
|
341
|
-
await wait(1000); // Match manual test timing
|
|
418
|
+
expect(hashA).toBe(hashB);
|
|
342
419
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
await wait(1000); // Match manual test timing
|
|
347
|
-
|
|
348
|
-
// A: Try to editAndRename a non-existent file (this is from the shrunk test case)
|
|
349
|
-
// This operation should be a no-op since aaa.txt doesn't exist
|
|
350
|
-
const fromPath = path.join(repoA, "aaa.txt");
|
|
351
|
-
const toPath = path.join(repoA, "aa/aa/aaa.txt");
|
|
352
|
-
if ((await pathExists(fromPath)) && !(await pathExists(toPath))) {
|
|
353
|
-
await fs.writeFile(fromPath, "");
|
|
354
|
-
await fs.mkdir(path.dirname(toPath), { recursive: true });
|
|
355
|
-
await fs.rename(fromPath, toPath);
|
|
356
|
-
console.log("Applied editAndRename to A");
|
|
357
|
-
} else {
|
|
358
|
-
console.log("Skipped editAndRename to A (file doesn't exist)");
|
|
359
|
-
}
|
|
420
|
+
// Both should have aaa.txt
|
|
421
|
+
expect(await pathExists(path.join(repoA, "aaa.txt"))).toBe(true);
|
|
422
|
+
expect(await pathExists(path.join(repoB, "aaa.txt"))).toBe(true);
|
|
360
423
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
// Pattern: A, B, A (like manual test that worked)
|
|
367
|
-
console.log("Round 1: A sync...");
|
|
368
|
-
const sync1 = await pushwork(["sync"], repoA);
|
|
369
|
-
console.log(
|
|
370
|
-
" A result:",
|
|
371
|
-
sync1.stdout.includes("already in sync") ? "no changes" : "had changes"
|
|
372
|
-
);
|
|
373
|
-
await wait(1000);
|
|
374
|
-
|
|
375
|
-
console.log("Round 2: B sync (should push aaa.txt)...");
|
|
376
|
-
|
|
377
|
-
// Check what B sees before sync
|
|
378
|
-
const bDiffBefore = await pushwork(["diff", "--name-only"], repoB);
|
|
379
|
-
console.log(
|
|
380
|
-
" B diff before sync:",
|
|
381
|
-
bDiffBefore.stdout
|
|
382
|
-
.split("\n")
|
|
383
|
-
.filter((l) => !l.includes("✓") && l.trim())
|
|
384
|
-
);
|
|
424
|
+
// Cleanup
|
|
425
|
+
tmpObj.removeCallback();
|
|
426
|
+
},
|
|
427
|
+
20000
|
|
428
|
+
);
|
|
385
429
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
430
|
+
it.concurrent(
|
|
431
|
+
"should handle files in subdirectories and moves between directories",
|
|
432
|
+
async () => {
|
|
433
|
+
const tmpObj = tmp.dirSync({ unsafeCleanup: true });
|
|
434
|
+
const testRoot = path.join(
|
|
435
|
+
tmpObj.name,
|
|
436
|
+
`test-subdir-${Date.now()}-${Math.random()}`
|
|
393
437
|
);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
438
|
+
await fs.mkdir(testRoot, { recursive: true });
|
|
439
|
+
const repoA = path.join(testRoot, "subdir-a");
|
|
440
|
+
const repoB = path.join(testRoot, "subdir-b");
|
|
441
|
+
await fs.mkdir(repoA);
|
|
442
|
+
await fs.mkdir(repoB);
|
|
443
|
+
|
|
444
|
+
// Initialize repo A with a file in a subdirectory
|
|
445
|
+
await fs.mkdir(path.join(repoA, "dir1"), { recursive: true });
|
|
446
|
+
await fs.writeFile(path.join(repoA, "dir1", "file1.txt"), "in dir1");
|
|
447
|
+
|
|
448
|
+
await pushwork(["init", "."], repoA);
|
|
449
|
+
await wait(500);
|
|
450
|
+
|
|
451
|
+
// Clone to B
|
|
452
|
+
const { stdout: rootUrl } = await pushwork(["url"], repoA);
|
|
453
|
+
await pushwork(["clone", rootUrl.trim(), repoB], tmpDir);
|
|
454
|
+
await wait(500);
|
|
455
|
+
|
|
456
|
+
// Verify B got the subdirectory and file
|
|
457
|
+
expect(await pathExists(path.join(repoB, "dir1", "file1.txt"))).toBe(
|
|
458
|
+
true
|
|
397
459
|
);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
" B result:",
|
|
404
|
-
sync2.stdout.includes("already in sync") ? "no changes" : "had changes"
|
|
405
|
-
);
|
|
406
|
-
console.log(" B full output:\n", sync2.stdout);
|
|
407
|
-
await wait(1000);
|
|
408
|
-
|
|
409
|
-
console.log("Round 3: A sync (should pull aaa.txt)...");
|
|
410
|
-
const sync3 = await pushwork(["sync"], repoA);
|
|
411
|
-
console.log(" A pulled?", sync3.stdout.includes("aaa.txt"));
|
|
412
|
-
console.log(
|
|
413
|
-
" A result:",
|
|
414
|
-
sync3.stdout.includes("already in sync") ? "no changes" : "had changes"
|
|
415
|
-
);
|
|
416
|
-
await wait(1000);
|
|
417
|
-
|
|
418
|
-
// Debug: Check what files exist
|
|
419
|
-
const filesA = await fs.readdir(repoA);
|
|
420
|
-
const filesB = await fs.readdir(repoB);
|
|
421
|
-
console.log(
|
|
422
|
-
"Files in A after sync:",
|
|
423
|
-
filesA.filter((f) => !f.startsWith("."))
|
|
424
|
-
);
|
|
425
|
-
console.log(
|
|
426
|
-
"Files in B after sync:",
|
|
427
|
-
filesB.filter((f) => !f.startsWith("."))
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
// Check diff
|
|
431
|
-
const { stdout: diffA } = await pushwork(["diff", "--name-only"], repoA);
|
|
432
|
-
const { stdout: diffB } = await pushwork(["diff", "--name-only"], repoB);
|
|
433
|
-
console.log("Diff A:", diffA.trim());
|
|
434
|
-
console.log("Diff B:", diffB.trim());
|
|
435
|
-
|
|
436
|
-
// Verify convergence
|
|
437
|
-
const hashA = await hashDirectory(repoA);
|
|
438
|
-
const hashB = await hashDirectory(repoB);
|
|
439
|
-
|
|
440
|
-
console.log("Hash A:", hashA);
|
|
441
|
-
console.log("Hash B:", hashB);
|
|
442
|
-
|
|
443
|
-
expect(hashA).toBe(hashB);
|
|
444
|
-
|
|
445
|
-
// Both should have aaa.txt
|
|
446
|
-
expect(await pathExists(path.join(repoA, "aaa.txt"))).toBe(true);
|
|
447
|
-
expect(await pathExists(path.join(repoB, "aaa.txt"))).toBe(true);
|
|
448
|
-
}, 30000);
|
|
449
|
-
|
|
450
|
-
it("should handle files in subdirectories and moves between directories", async () => {
|
|
451
|
-
const repoA = path.join(tmpDir, "subdir-a");
|
|
452
|
-
const repoB = path.join(tmpDir, "subdir-b");
|
|
453
|
-
await fs.mkdir(repoA);
|
|
454
|
-
await fs.mkdir(repoB);
|
|
455
|
-
|
|
456
|
-
// Initialize repo A with a file in a subdirectory
|
|
457
|
-
await fs.mkdir(path.join(repoA, "dir1"), { recursive: true });
|
|
458
|
-
await fs.writeFile(path.join(repoA, "dir1", "file1.txt"), "in dir1");
|
|
459
|
-
|
|
460
|
-
await pushwork(["init", "."], repoA);
|
|
461
|
-
await wait(500);
|
|
462
|
-
|
|
463
|
-
// Clone to B
|
|
464
|
-
const { stdout: rootUrl } = await pushwork(["url"], repoA);
|
|
465
|
-
await pushwork(["clone", rootUrl.trim(), repoB], tmpDir);
|
|
466
|
-
await wait(500);
|
|
460
|
+
const initialContentB = await fs.readFile(
|
|
461
|
+
path.join(repoB, "dir1", "file1.txt"),
|
|
462
|
+
"utf-8"
|
|
463
|
+
);
|
|
464
|
+
expect(initialContentB).toBe("in dir1");
|
|
467
465
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
);
|
|
472
|
-
const initialContentB = await fs.readFile(
|
|
473
|
-
path.join(repoB, "dir1", "file1.txt"),
|
|
474
|
-
"utf-8"
|
|
475
|
-
);
|
|
476
|
-
expect(initialContentB).toBe("in dir1");
|
|
466
|
+
// On A: Create another file in a different subdirectory
|
|
467
|
+
await fs.mkdir(path.join(repoA, "dir2"), { recursive: true });
|
|
468
|
+
await fs.writeFile(path.join(repoA, "dir2", "file2.txt"), "in dir2");
|
|
477
469
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
470
|
+
// Sync both sides
|
|
471
|
+
await pushwork(["sync"], repoA);
|
|
472
|
+
await wait(1000);
|
|
473
|
+
await pushwork(["sync"], repoB);
|
|
474
|
+
await wait(1000);
|
|
481
475
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
476
|
+
// Verify B got the new subdirectory and file
|
|
477
|
+
expect(await pathExists(path.join(repoB, "dir2", "file2.txt"))).toBe(
|
|
478
|
+
true
|
|
479
|
+
);
|
|
480
|
+
const file2ContentB = await fs.readFile(
|
|
481
|
+
path.join(repoB, "dir2", "file2.txt"),
|
|
482
|
+
"utf-8"
|
|
483
|
+
);
|
|
484
|
+
expect(file2ContentB).toBe("in dir2");
|
|
487
485
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
path.join(repoB, "dir2", "file2.txt"),
|
|
494
|
-
"utf-8"
|
|
495
|
-
);
|
|
496
|
-
expect(file2ContentB).toBe("in dir2");
|
|
497
|
-
}, 30000);
|
|
486
|
+
// Cleanup
|
|
487
|
+
tmpObj.removeCallback();
|
|
488
|
+
},
|
|
489
|
+
30000
|
|
490
|
+
);
|
|
498
491
|
});
|
|
499
492
|
|
|
500
493
|
describe("Property-Based Fuzzing with fast-check", () => {
|
|
@@ -684,24 +677,13 @@ describe("Pushwork Fuzzer", () => {
|
|
|
684
677
|
await fs.mkdir(repoA);
|
|
685
678
|
await fs.mkdir(repoB);
|
|
686
679
|
|
|
687
|
-
const testStart = Date.now();
|
|
688
|
-
console.log(
|
|
689
|
-
`\n🔬 Testing: ${opsA.length} ops on A, ${opsB.length} ops on B`
|
|
690
|
-
);
|
|
691
|
-
|
|
692
680
|
try {
|
|
693
681
|
// Initialize repo A with an initial file
|
|
694
|
-
console.log(
|
|
695
|
-
` ⏱️ [${Date.now() - testStart}ms] Initializing repo A...`
|
|
696
|
-
);
|
|
697
682
|
await fs.writeFile(path.join(repoA, "initial.txt"), "initial");
|
|
698
683
|
await pushwork(["init", "."], repoA);
|
|
699
684
|
await wait(500);
|
|
700
685
|
|
|
701
686
|
// Get root URL and clone to B
|
|
702
|
-
console.log(
|
|
703
|
-
` ⏱️ [${Date.now() - testStart}ms] Cloning to repo B...`
|
|
704
|
-
);
|
|
705
687
|
const { stdout: rootUrl } = await pushwork(["url"], repoA);
|
|
706
688
|
const cleanRootUrl = rootUrl.trim();
|
|
707
689
|
await pushwork(["clone", cleanRootUrl, repoB], testRoot);
|
|
@@ -711,105 +693,43 @@ describe("Pushwork Fuzzer", () => {
|
|
|
711
693
|
const hashBeforeOps = await hashDirectory(repoA);
|
|
712
694
|
const hashB1 = await hashDirectory(repoB);
|
|
713
695
|
expect(hashBeforeOps).toBe(hashB1);
|
|
714
|
-
console.log(
|
|
715
|
-
` ⏱️ [${Date.now() - testStart}ms] Initial state verified`
|
|
716
|
-
);
|
|
717
696
|
|
|
718
697
|
// Apply operations to both sides
|
|
719
|
-
console.log(
|
|
720
|
-
` ⏱️ [${Date.now() - testStart}ms] Applying ${
|
|
721
|
-
opsA.length
|
|
722
|
-
} operations to repo A...`
|
|
723
|
-
);
|
|
724
|
-
console.log(` Operations A: ${JSON.stringify(opsA)}`);
|
|
725
698
|
await applyOperations(repoA, opsA);
|
|
726
699
|
|
|
727
|
-
console.log(
|
|
728
|
-
` ⏱️ [${Date.now() - testStart}ms] Applying ${
|
|
729
|
-
opsB.length
|
|
730
|
-
} operations to repo B...`
|
|
731
|
-
);
|
|
732
|
-
console.log(` Operations B: ${JSON.stringify(opsB)}`);
|
|
733
700
|
await applyOperations(repoB, opsB);
|
|
734
701
|
|
|
735
702
|
// Multiple sync rounds for convergence
|
|
736
703
|
// Need enough time for network propagation between CLI invocations
|
|
737
704
|
// Round 1: A pushes changes
|
|
738
|
-
console.log(
|
|
739
|
-
` ⏱️ [${Date.now() - testStart}ms] Sync round 1: A...`
|
|
740
|
-
);
|
|
741
705
|
await pushwork(["sync"], repoA);
|
|
742
|
-
await wait(
|
|
706
|
+
await wait(1000);
|
|
743
707
|
|
|
744
708
|
// Round 2: B pushes changes and pulls A's changes
|
|
745
|
-
console.log(
|
|
746
|
-
` ⏱️ [${Date.now() - testStart}ms] Sync round 1: B...`
|
|
747
|
-
);
|
|
748
709
|
await pushwork(["sync"], repoB);
|
|
749
|
-
await wait(
|
|
710
|
+
await wait(1000);
|
|
750
711
|
|
|
751
712
|
// Round 3: A pulls B's changes
|
|
752
|
-
console.log(
|
|
753
|
-
` ⏱️ [${Date.now() - testStart}ms] Sync round 2: A...`
|
|
754
|
-
);
|
|
755
713
|
await pushwork(["sync"], repoA);
|
|
756
|
-
await wait(
|
|
714
|
+
await wait(1000);
|
|
757
715
|
|
|
758
716
|
// Round 4: B confirms convergence
|
|
759
|
-
console.log(
|
|
760
|
-
` ⏱️ [${Date.now() - testStart}ms] Sync round 2: B...`
|
|
761
|
-
);
|
|
762
717
|
await pushwork(["sync"], repoB);
|
|
763
|
-
await wait(
|
|
718
|
+
await wait(1000);
|
|
764
719
|
|
|
765
720
|
// Round 5: Final convergence check
|
|
766
|
-
console.log(
|
|
767
|
-
` ⏱️ [${Date.now() - testStart}ms] Sync round 3: A (final)...`
|
|
768
|
-
);
|
|
769
721
|
await pushwork(["sync"], repoA);
|
|
770
|
-
await wait(
|
|
722
|
+
await wait(1000);
|
|
771
723
|
|
|
772
724
|
// Round 6: Extra convergence check (for aggressive fuzzing)
|
|
773
|
-
console.log(
|
|
774
|
-
` ⏱️ [${Date.now() - testStart}ms] Sync round 3: B (final)...`
|
|
775
|
-
);
|
|
776
725
|
await pushwork(["sync"], repoB);
|
|
777
|
-
await wait(
|
|
726
|
+
await wait(5000);
|
|
778
727
|
|
|
779
728
|
// Verify final state matches
|
|
780
|
-
console.log(
|
|
781
|
-
` ⏱️ [${Date.now() - testStart}ms] Verifying convergence...`
|
|
782
|
-
);
|
|
783
729
|
|
|
784
730
|
const hashAfterA = await hashDirectory(repoA);
|
|
785
731
|
const hashAfterB = await hashDirectory(repoB);
|
|
786
732
|
|
|
787
|
-
console.log(` Hash A: ${hashAfterA.substring(0, 16)}...`);
|
|
788
|
-
console.log(` Hash B: ${hashAfterB.substring(0, 16)}...`);
|
|
789
|
-
|
|
790
|
-
// Both sides should converge to the same state
|
|
791
|
-
if (hashAfterA !== hashAfterB) {
|
|
792
|
-
// Show what files are different
|
|
793
|
-
const filesA = await getAllFiles(repoA);
|
|
794
|
-
const filesB = await getAllFiles(repoB);
|
|
795
|
-
console.log(` ❌ CONVERGENCE FAILURE!`);
|
|
796
|
-
console.log(
|
|
797
|
-
` Files in A: ${filesA
|
|
798
|
-
.filter((f) => !f.includes(".pushwork"))
|
|
799
|
-
.join(", ")}`
|
|
800
|
-
);
|
|
801
|
-
console.log(
|
|
802
|
-
` Files in B: ${filesB
|
|
803
|
-
.filter((f) => !f.includes(".pushwork"))
|
|
804
|
-
.join(", ")}`
|
|
805
|
-
);
|
|
806
|
-
console.log(
|
|
807
|
-
` Operations applied to A: ${JSON.stringify(opsA)}`
|
|
808
|
-
);
|
|
809
|
-
console.log(
|
|
810
|
-
` Operations applied to B: ${JSON.stringify(opsB)}`
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
733
|
expect(hashAfterA).toBe(hashAfterB);
|
|
814
734
|
|
|
815
735
|
// Verify diff shows no changes
|
|
@@ -829,9 +749,6 @@ describe("Pushwork Fuzzer", () => {
|
|
|
829
749
|
);
|
|
830
750
|
expect(diffLines.length).toBe(0);
|
|
831
751
|
|
|
832
|
-
const totalTime = Date.now() - testStart;
|
|
833
|
-
console.log(` ✅ Converged successfully! (took ${totalTime}ms)`);
|
|
834
|
-
|
|
835
752
|
// Cleanup
|
|
836
753
|
await fs.rm(testRoot, { recursive: true, force: true });
|
|
837
754
|
} catch (error) {
|
|
@@ -844,13 +761,13 @@ describe("Pushwork Fuzzer", () => {
|
|
|
844
761
|
}
|
|
845
762
|
),
|
|
846
763
|
{
|
|
847
|
-
numRuns:
|
|
848
|
-
timeout:
|
|
764
|
+
numRuns: 5, // INTENSE MODE (was 20, then cranked to 50)
|
|
765
|
+
timeout: 60000, // 1 minute timeout per run
|
|
849
766
|
verbose: true, // Verbose output
|
|
850
767
|
endOnFailure: true, // Stop on first failure to debug
|
|
851
768
|
}
|
|
852
769
|
);
|
|
853
|
-
},
|
|
770
|
+
}, 600000); // 10 minute timeout for the whole test
|
|
854
771
|
});
|
|
855
772
|
});
|
|
856
773
|
|