v0.2.0: semaphore timeouts, error logging, dead code removal, parallel exports
Critical: - Replace DISPATCH_TIME_FOREVER with 120s/30s timeouts in ObjC - Log failed asset IDs and error messages in cmdExport/backupTree - Show failed count in export summaries Cleanup: - Remove legacy Bridge methods (ExportAlbumPreviews, ExportAlbumOriginals, BackupAll) - Remove legacy ObjC functions and C stub equivalents - Remove photos.go delegates (package-level pass-throughs) - Remove InterpretExportResult (only used by legacy methods) - Clean up mockBridge fields (rename Fn2 -> Fn) - Fix rc race condition in main_main.go (atomic.Int32) - Remove unused variables (_ = grandTotal, _ = sig) Design: - Fix resolveAlbumID: ListAlbums first (cheap), then direct ID - Unify Cloud type: Asset.Cloud string (was bool) - Extract shared export logic into exportAssets/exportOne - Add worker pool for parallel exports (3 workers when assets >= 4) - Fix backupTree progress bar counter and directory prefix Robustness: - Add nil checks for stringWithUTF8String: in ObjC - Log directory creation errors in ensure_directory (ObjC) Quality: - Add go vet and -race flag to Makefile test target - Add ADR for performSelector cloudIdentifier decision - Add sync comments between Go/ObjC sanitizePathComponent - Add package-level doc comment - Add tests: partial failure, skipped album, album-not-found message
This commit is contained in:
@@ -209,45 +209,6 @@ func TestParseTreeJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretExportResult(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
rc int
|
||||
wantN int
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{name: "success", rc: 5, wantN: 5},
|
||||
{name: "zero exports", rc: 0, wantN: 0},
|
||||
{name: "single export", rc: 1, wantN: 1},
|
||||
{name: "invalid args", rc: -1, wantErr: true, errMsg: "invalid arguments or directory creation failed"},
|
||||
{name: "mkdir failed", rc: -2, wantErr: true, errMsg: "could not create output directory"},
|
||||
{name: "album not found", rc: -3, wantErr: true, errMsg: "album not found"},
|
||||
{name: "all exports failed", rc: -4, wantErr: true, errMsg: "all exports failed"},
|
||||
{name: "cancelled", rc: -5, wantErr: true, errMsg: "cancelled"},
|
||||
{name: "unknown error", rc: -99, wantErr: true, errMsg: "unknown error (code -99)"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
n, err := InterpretExportResult(tt.rc)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("InterpretExportResult() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err.Error() != tt.errMsg {
|
||||
t.Errorf("InterpretExportResult() error = %q, want %q", err.Error(), tt.errMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
if n != tt.wantN {
|
||||
t.Errorf("InterpretExportResult() = %d, want %d", n, tt.wantN)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlbumsResponseUnmarshal(t *testing.T) {
|
||||
raw := `{"albums":[{"id":"x","title":"Y"}]}`
|
||||
var resp AlbumsResponse
|
||||
@@ -299,68 +260,6 @@ func TestCgoBridgeImplementsBridge(t *testing.T) {
|
||||
var _ Bridge = &CgoBridge{}
|
||||
}
|
||||
|
||||
func TestPackageLevelDelegatesToDefaultBridge(t *testing.T) {
|
||||
SetTestAccessRC(0)
|
||||
SetTestAlbumsJSON(`{"albums":[{"id":"p1","title":"PAlbum"}]}`)
|
||||
SetTestAssetsJSON(`{"assets":[{"id":"pa1","filename":"IMG_1001.JPG"}],"total":1}`)
|
||||
SetTestTreeJSON(`{"collections":[{"id":"f1","name":"Trips","kind":"folder","children":[{"id":"a1","name":"Venice","kind":"album"}]}]}`)
|
||||
SetTestExportRC(3)
|
||||
SetTestExportOriginalsRC(2)
|
||||
SetTestBackupAllRC(5)
|
||||
|
||||
if err := RequestAccess(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
albums, err := ListAlbums()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(albums) != 1 || albums[0].ID != "p1" {
|
||||
t.Errorf("got %+v", albums)
|
||||
}
|
||||
|
||||
assets, _, err := ListAssets("p1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(assets) != 1 || assets[0].ID != "pa1" || assets[0].Filename != "IMG_1001.JPG" {
|
||||
t.Errorf("got %+v", assets)
|
||||
}
|
||||
|
||||
tree, err := ListTree()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(tree) != 1 || tree[0].ID != "f1" || tree[0].Name != "Trips" || len(tree[0].Children) != 1 || tree[0].Children[0].ID != "a1" || tree[0].Children[0].Name != "Venice" {
|
||||
t.Errorf("got %+v", tree)
|
||||
}
|
||||
|
||||
n, err := ExportAlbumPreviews("p1", "/tmp/x", 1024)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 3 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
|
||||
n, err = ExportAlbumOriginals("p1", "/tmp/x")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 2 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
|
||||
n, err = BackupAll("/tmp/x", 1024, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 5 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoBridgeListAlbumsViaStub(t *testing.T) {
|
||||
SetTestAlbumsJSON(`{"albums":[{"id":"abc","title":"TestAlbum"}]}`)
|
||||
bridge := &CgoBridge{}
|
||||
@@ -425,69 +324,6 @@ func TestCgoBridgeListTreeNil(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoBridgeExportViaStub(t *testing.T) {
|
||||
SetTestExportRC(7)
|
||||
bridge := &CgoBridge{}
|
||||
n, err := bridge.ExportAlbumPreviews("id", "/tmp/out", 512)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 7 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoBridgeExportErrorViaStub(t *testing.T) {
|
||||
SetTestExportRC(-3)
|
||||
bridge := &CgoBridge{}
|
||||
_, err := bridge.ExportAlbumPreviews("id", "/tmp/out", 512)
|
||||
if err == nil || err.Error() != "album not found" {
|
||||
t.Errorf("got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoBridgeExportOriginalsViaStub(t *testing.T) {
|
||||
SetTestExportOriginalsRC(6)
|
||||
bridge := &CgoBridge{}
|
||||
n, err := bridge.ExportAlbumOriginals("id", "/tmp/out")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 6 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoBridgeExportOriginalsErrorViaStub(t *testing.T) {
|
||||
SetTestExportOriginalsRC(-4)
|
||||
bridge := &CgoBridge{}
|
||||
_, err := bridge.ExportAlbumOriginals("id", "/tmp/out")
|
||||
if err == nil || err.Error() != "all exports failed" {
|
||||
t.Errorf("got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoBridgeBackupAllViaStub(t *testing.T) {
|
||||
SetTestBackupAllRC(8)
|
||||
bridge := &CgoBridge{}
|
||||
n, err := bridge.BackupAll("/tmp/out", 1024, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 8 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoBridgeBackupAllErrorViaStub(t *testing.T) {
|
||||
SetTestBackupAllRC(-2)
|
||||
bridge := &CgoBridge{}
|
||||
_, err := bridge.BackupAll("/tmp/out", 1024, true)
|
||||
if err == nil || err.Error() != "could not create output directory" {
|
||||
t.Errorf("got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoBridgeRequestAccessGranted(t *testing.T) {
|
||||
SetTestAccessRC(0)
|
||||
bridge := &CgoBridge{}
|
||||
|
||||
Reference in New Issue
Block a user