| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "testing" |
| 8 | "time" |
| 9 | |
| 10 | "reasonix/internal/config" |
| 11 | "reasonix/internal/sessioncatalog" |
| 12 | ) |
| 13 | |
| 14 | func TestRebuildSessionCatalogDoesNotReplaceBeforeOldReconcileStops(t *testing.T) { |
| 15 | isolateDesktopUserDirs(t) |
| 16 | dir := config.SessionDir() |
| 17 | if err := os.MkdirAll(dir, 0o755); err != nil { |
| 18 | t.Fatal(err) |
| 19 | } |
| 20 | |
| 21 | app := NewApp() |
| 22 | app.startSessionCatalog() |
| 23 | oldCatalog := waitForSessionCatalogForTest(t, app, nil) |
| 24 | t.Cleanup(func() { app.stopSessionCatalog(time.Second) }) |
| 25 | |
| 26 | catalogPath := sessioncatalog.DefaultPath() |
| 27 | before, err := os.Stat(catalogPath) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | started := make(chan struct{}) |
| 32 | release := make(chan struct{}) |
| 33 | finished := make(chan struct{}) |
| 34 | app.catalogReconcileHook = func(sessioncatalog.DirectoryTarget) { |
| 35 | close(started) |
| 36 | <-release |
| 37 | } |
| 38 | app.catalogReconcileDoneHook = func(sessioncatalog.DirectoryTarget) { close(finished) } |
| 39 | if !app.requestSessionCatalogReconcile(dir) { |
| 40 | t.Fatal("explicit reconcile was not scheduled") |
| 41 | } |
| 42 | <-started |
| 43 | |
| 44 | stopTimeout := 25 * time.Millisecond |
| 45 | err = app.rebuildSessionCatalog(stopTimeout) |
| 46 | if !errors.Is(err, errSessionCatalogStopTimeout) { |
| 47 | close(release) |
| 48 | t.Fatalf("rebuild error = %v, want stop deadline error", err) |
| 49 | } |
| 50 | if app.catalogRebuilding.Load() { |
| 51 | close(release) |
| 52 | t.Fatal("timed-out rebuild left rebuilding set") |
| 53 | } |
| 54 | after, err := os.Stat(catalogPath) |
| 55 | if err != nil { |
| 56 | close(release) |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | if !os.SameFile(before, after) { |
| 60 | close(release) |
| 61 | t.Fatal("rebuild replaced the live projection before the old reconcile stopped") |
| 62 | } |
| 63 | if backups, err := filepath.Glob(catalogPath + ".replaced-*"); err != nil || len(backups) != 0 { |
| 64 | close(release) |
| 65 | t.Fatalf("replacement backups after aborted rebuild = %v, err = %v", backups, err) |
| 66 | } |
| 67 | |
| 68 | close(release) |
| 69 | select { |
| 70 | case <-finished: |
| 71 | case <-time.After(5 * time.Second): |
| 72 | t.Fatal("old reconcile did not finish after release") |
| 73 | } |
| 74 | assertSessionCatalogWatcherRunning(t, app) |
| 75 | _ = waitForSessionCatalogForTest(t, app, oldCatalog) |
| 76 | } |
| 77 |