package exif import ( "testing" "reflect" "bytes" "path" "fmt" "strings" "github.com/dsoprea/go-logging" ) func TestAdd(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) originalBytes := []byte { 0x11, 0x22, 0x33 } bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x44, value: NewIfdBuilderTagValueFromBytes([]byte(originalBytes)), } err = ib.Add(bt) log.PanicIf(err) if ib.ii != RootIi { t.Fatalf("IFD name not correct.") } else if ib.ifdTagId != 0 { t.Fatalf("IFD tag-ID not correct.") } else if ib.byteOrder != TestDefaultByteOrder { t.Fatalf("IFD byte-order not correct.") } else if len(ib.tags) != 4 { t.Fatalf("IFD tag-count not correct.") } else if ib.existingOffset != 0 { t.Fatalf("IFD offset not correct.") } else if ib.nextIb != nil { t.Fatalf("Next-IFD not correct.") } tags := ib.Tags() if tags[0].tagId != 0x11 { t.Fatalf("tag (0) tag-ID not correct") } else if bytes.Compare(tags[0].value.Bytes(), []byte("test string")) != 0 { t.Fatalf("tag (0) value not correct") } if tags[1].tagId != 0x22 { t.Fatalf("tag (1) tag-ID not correct") } else if bytes.Compare(tags[1].value.Bytes(), []byte("test string2")) != 0 { t.Fatalf("tag (1) value not correct") } if tags[2].tagId != 0x33 { t.Fatalf("tag (2) tag-ID not correct") } else if bytes.Compare(tags[2].value.Bytes(), []byte("test string3")) != 0 { t.Fatalf("tag (2) value not correct") } if tags[3].tagId != 0x44 { t.Fatalf("tag (3) tag-ID not correct") } else if bytes.Compare(tags[3].value.Bytes(), originalBytes) != 0 { t.Fatalf("tag (3) value not correct") } } func TestSetNextIb(t *testing.T) { ib1 := NewIfdBuilder(RootIi, TestDefaultByteOrder) ib2 := NewIfdBuilder(RootIi, TestDefaultByteOrder) if ib1.nextIb != nil { t.Fatalf("Next-IFD for IB1 not initially terminal.") } err := ib1.SetNextIb(ib2) log.PanicIf(err) if ib1.nextIb != ib2 { t.Fatalf("Next-IFD for IB1 not correct.") } else if ib2.nextIb != nil { t.Fatalf("Next-IFD for IB2 terminal.") } } func TestAddChildIb(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) exifIi, _ := IfdIdOrFail(IfdStandard, IfdExif) ibChild := NewIfdBuilder(exifIi, TestDefaultByteOrder) err = ib.AddChildIb(ibChild) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) if ib.tags[0].tagId != 0x11 { t.Fatalf("first tag not correct") } else if ib.tags[1].tagId != ibChild.ifdTagId { t.Fatalf("second tag ID does not match child-IFD tag-ID: (0x%04x) != (0x%04x)", ib.tags[1].tagId, ibChild.ifdTagId) } else if ib.tags[1].value.Ib() != ibChild { t.Fatalf("second tagvalue does not match child-IFD") } else if ib.tags[2].tagId != 0x22 { t.Fatalf("third tag not correct") } } func TestAddTagsFromExisting(t *testing.T) { defer func() { if state := recover(); state != nil { err := log.Wrap(state.(error)) log.PrintErrorf(err, "Test failure.") } }() ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) entries := make([]*IfdTagEntry, 3) entries[0] = &IfdTagEntry{ Ii: ExifIi, TagId: 0x11, TagType: TypeByte, UnitCount: 4, RawValueOffset: []byte { 0x12, 0, 0, 0 }, } entries[1] = &IfdTagEntry{ Ii: ExifIi, TagId: 0x22, TagType: TypeLong, ChildIfdName: "some ifd", } entries[2] = &IfdTagEntry{ Ii: ExifIi, TagId: 0x33, TagType: TypeByte, UnitCount: 4, RawValueOffset: []byte { 0x34, 0, 0, 0 }, } ifd := &Ifd{ Ii: RootIi, Entries: entries, } err := ib.AddTagsFromExisting(ifd, nil, nil, nil) log.PanicIf(err) if ib.tags[0].tagId != 0x11 { t.Fatalf("tag (0) not correct") } else if ib.tags[1].tagId != 0x22 { t.Fatalf("tag (1) not correct") } else if ib.tags[2].tagId != 0x33 { t.Fatalf("tag (2) not correct") } else if len(ib.tags) != 3 { t.Fatalf("tag count not correct") } } func TestAddTagsFromExisting__Includes(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) entries := make([]*IfdTagEntry, 3) entries[0] = &IfdTagEntry{ Ii: RootIi, TagType: TypeByte, TagId: 0x11, } entries[1] = &IfdTagEntry{ Ii: RootIi, TagType: TypeByte, TagId: 0x22, ChildIfdName: "some ifd", } entries[2] = &IfdTagEntry{ Ii: RootIi, TagType: TypeByte, TagId: 0x33, } ifd := &Ifd{ Ii: RootIi, Entries: entries, } err := ib.AddTagsFromExisting(ifd, nil, []uint16 { 0x33 }, nil) log.PanicIf(err) if ib.tags[0].tagId != 0x33 { t.Fatalf("tag (1) not correct") } else if len(ib.tags) != 1 { t.Fatalf("tag count not correct") } } func TestAddTagsFromExisting__Excludes(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) entries := make([]*IfdTagEntry, 3) entries[0] = &IfdTagEntry{ Ii: RootIi, TagType: TypeByte, TagId: 0x11, } entries[1] = &IfdTagEntry{ Ii: RootIi, TagType: TypeByte, TagId: 0x22, ChildIfdName: "some ifd", } entries[2] = &IfdTagEntry{ Ii: RootIi, TagType: TypeByte, TagId: 0x33, } ifd := &Ifd{ Ii: RootIi, Entries: entries, } err := ib.AddTagsFromExisting(ifd, nil, nil, []uint16 { 0x11 }) log.PanicIf(err) if ib.tags[0].tagId != 0x22 { t.Fatalf("tag not correct") } else if len(ib.tags) != 2 { t.Fatalf("tag count not correct") } } func TestFindN_First_1(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x11, 1) log.PanicIf(err) if len(found) != 1 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 0 { log.Panicf("Result was not in the right place: (%d)", found[0]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x11 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestFindN_First_2_1Returned(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x11, 2) log.PanicIf(err) if len(found) != 1 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 0 { log.Panicf("Result was not in the right place: (%d)", found[0]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x11 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestFindN_First_2_2Returned(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string5")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x11, 2) log.PanicIf(err) if len(found) != 2 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 0 { log.Panicf("First result was not in the right place: (%d)", found[0]) } else if found[1] != 3 { log.Panicf("Second result was not in the right place: (%d)", found[1]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x11 || bytes.Compare(bt.value.Bytes(), []byte("test string")) != 0 { log.Panicf("Found entry 0 is not correct: (0x%04x) [%s]", bt.tagId, bt.value) } bt = tags[found[1]] if bt.tagId != 0x11 || bytes.Compare(bt.value.Bytes(), []byte("test string4")) != 0 { log.Panicf("Found entry 1 is not correct: (0x%04x) [%s]", bt.tagId, bt.value) } } func TestFindN_Middle_WithDuplicates(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string5")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string6")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x33, 1) log.PanicIf(err) if len(found) != 1 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 2 { log.Panicf("Result was not in the right place: (%d)", found[0]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x33 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestFindN_Middle_NoDuplicates(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x33, 1) log.PanicIf(err) if len(found) != 1 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 2 { log.Panicf("Result was not in the right place: (%d)", found[0]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x33 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestFindN_Miss(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) found, err := ib.FindN(0x11, 1) log.PanicIf(err) if len(found) != 0 { t.Fatalf("Expected empty results.") } } func TestFind_Hit(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) position, err := ib.Find(0x33) log.PanicIf(err) if position != 2 { log.Panicf("Result was not in the right place: (%d)", position) } tags := ib.Tags() bt = tags[position] if bt.tagId != 0x33 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestFind_Miss(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) _, err = ib.Find(0x99) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func TestReplace(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) currentIds := make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x22, 0x33 }, currentIds) == false { t.Fatalf("Pre-replace tags are not correct.") } bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x99, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Replace(0x22, bt) log.PanicIf(err) currentIds = make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x99, 0x33 }, currentIds) == false { t.Fatalf("Post-replace tags are not correct.") } } func TestReplaceN(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) currentIds := make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x22, 0x33 }, currentIds) == false { t.Fatalf("Pre-replace tags are not correct.") } bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0xA9, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.ReplaceAt(1, bt) log.PanicIf(err) currentIds = make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0xA9, 0x33 }, currentIds) == false { t.Fatalf("Post-replace tags are not correct.") } } func TestDeleteFirst(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) if len(ib.Tags()) != 4 { t.Fatalf("Pre-delete tag count not correct.") } currentIds := make([]uint16, 4) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x22, 0x22, 0x33 }, currentIds) == false { t.Fatalf("Pre-delete tags not correct.") } err = ib.DeleteFirst(0x22) log.PanicIf(err) if len(ib.Tags()) != 3 { t.Fatalf("Post-delete (1) tag count not correct.") } currentIds = make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x22, 0x33 }, currentIds) == false { t.Fatalf("Post-delete (1) tags not correct.") } err = ib.DeleteFirst(0x22) log.PanicIf(err) if len(ib.Tags()) != 2 { t.Fatalf("Post-delete (2) tag count not correct.") } currentIds = make([]uint16, 2) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x33 }, currentIds) == false { t.Fatalf("Post-delete (2) tags not correct.") } err = ib.DeleteFirst(0x22) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func TestDeleteN(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) if len(ib.Tags()) != 4 { t.Fatalf("Pre-delete tag count not correct.") } currentIds := make([]uint16, 4) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x22, 0x22, 0x33 }, currentIds) == false { t.Fatalf("Pre-delete tags not correct.") } err = ib.DeleteN(0x22, 1) log.PanicIf(err) if len(ib.Tags()) != 3 { t.Fatalf("Post-delete (1) tag count not correct.") } currentIds = make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x22, 0x33 }, currentIds) == false { t.Fatalf("Post-delete (1) tags not correct.") } err = ib.DeleteN(0x22, 1) log.PanicIf(err) if len(ib.Tags()) != 2 { t.Fatalf("Post-delete (2) tag count not correct.") } currentIds = make([]uint16, 2) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x33 }, currentIds) == false { t.Fatalf("Post-delete (2) tags not correct.") } err = ib.DeleteN(0x22, 1) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func TestDeleteN_Two(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) if len(ib.Tags()) != 4 { t.Fatalf("Pre-delete tag count not correct.") } currentIds := make([]uint16, 4) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x22, 0x22, 0x33 }, currentIds) == false { t.Fatalf("Pre-delete tags not correct.") } err = ib.DeleteN(0x22, 2) log.PanicIf(err) if len(ib.Tags()) != 2 { t.Fatalf("Post-delete tag count not correct.") } currentIds = make([]uint16, 2) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x33 }, currentIds) == false { t.Fatalf("Post-delete tags not correct.") } err = ib.DeleteFirst(0x22) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func TestDeleteAll(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) bt := builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err := ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = builderTag{ ii: RootIi, typeId: TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) if len(ib.Tags()) != 4 { t.Fatalf("Pre-delete tag count not correct.") } currentIds := make([]uint16, 4) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x22, 0x22, 0x33 }, currentIds) == false { t.Fatalf("Pre-delete tags not correct.") } n, err := ib.DeleteAll(0x22) log.PanicIf(err) if n != 2 { t.Fatalf("Returned delete tag count not correct.") } else if len(ib.Tags()) != 2 { t.Fatalf("Post-delete tag count not correct.") } currentIds = make([]uint16, 2) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16 { 0x11, 0x33 }, currentIds) == false { t.Fatalf("Post-delete tags not correct.") } err = ib.DeleteFirst(0x22) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func Test_IfdBuilder_CreateIfdBuilderFromExistingChain(t *testing.T) { defer func() { if state := recover(); state != nil { err := log.Wrap(state.(error)) log.PrintErrorf(err, "Test failure.") } }() e := NewExif() filepath := path.Join(assetsPath, "NDM_8901.jpg") rawExif, err := e.SearchAndExtractExif(filepath) log.PanicIf(err) _, index, err := e.Collect(rawExif) log.PanicIf(err) itevr := NewIfdTagEntryValueResolver(rawExif, index.RootIfd.ByteOrder) ib := NewIfdBuilderFromExistingChain(index.RootIfd, itevr) actual := ib.DumpToStrings() expected := []string { " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(0) TAG=[0x010f]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(1) TAG=[0x0110]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(2) TAG=[0x0112]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(3) TAG=[0x011a]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(4) TAG=[0x011b]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(5) TAG=[0x0128]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(6) TAG=[0x0132]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(7) TAG=[0x013b]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(8) TAG=[0x0213]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[] INDEX=(9) TAG=[0x8298]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[Exif] INDEX=(10) TAG=[0x8769]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(0) TAG=[0x829a]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(1) TAG=[0x829d]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(2) TAG=[0x8822]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(3) TAG=[0x8827]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(4) TAG=[0x8830]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(5) TAG=[0x8832]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(6) TAG=[0x9000]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(7) TAG=[0x9003]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(8) TAG=[0x9004]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(9) TAG=[0x9101]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(10) TAG=[0x9201]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(11) TAG=[0x9202]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(12) TAG=[0x9204]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(13) TAG=[0x9207]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(14) TAG=[0x9209]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(15) TAG=[0x920a]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(16) TAG=[0x927c]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(17) TAG=[0x9286]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(18) TAG=[0x9290]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(19) TAG=[0x9291]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(20) TAG=[0x9292]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(21) TAG=[0xa000]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(22) TAG=[0xa001]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(23) TAG=[0xa002]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(24) TAG=[0xa003]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[Iop] INDEX=(25) TAG=[0xa005]", "Exif] IFD-NAME=[Iop]> IFD-TAG-ID=(0xa005) CHILD-IFD=[] INDEX=(0) TAG=[0x0001]", "Exif] IFD-NAME=[Iop]> IFD-TAG-ID=(0xa005) CHILD-IFD=[] INDEX=(1) TAG=[0x0002]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(26) TAG=[0xa20e]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(27) TAG=[0xa20f]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(28) TAG=[0xa210]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(29) TAG=[0xa401]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(30) TAG=[0xa402]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(31) TAG=[0xa403]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(32) TAG=[0xa406]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(33) TAG=[0xa430]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(34) TAG=[0xa431]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(35) TAG=[0xa432]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(36) TAG=[0xa434]", " IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(37) TAG=[0xa435]", " IFD-TAG-ID=(0x0000) CHILD-IFD=[GPSInfo] INDEX=(11) TAG=[0x8825]", " IFD-TAG-ID=(0x8825) CHILD-IFD=[] INDEX=(0) TAG=[0x0000]", } if reflect.DeepEqual(actual, expected) == false { fmt.Printf("ACTUAL:\n%s\n\nEXPECTED:\n%s\n", strings.Join(actual, "\n"), strings.Join(expected, "\n")) t.Fatalf("IB did not [correctly] duplicate the IFD structure.") } } // TODO(dustin): !! Test with an actual GPS-attached image. func Test_IfdBuilder_CreateIfdBuilderFromExistingChain_RealData(t *testing.T) { filepath := path.Join(assetsPath, "NDM_8901.jpg") e := NewExif() rawExif, err := e.SearchAndExtractExif(filepath) log.PanicIf(err) // Decode from binary. _, originalIndex, err := e.Collect(rawExif) log.PanicIf(err) originalThumbnailData, err := originalIndex.RootIfd.NextIfd.Thumbnail() log.PanicIf(err) originalTags := originalIndex.RootIfd.DumpTags() // Encode back to binary. ibe := NewIfdByteEncoder() itevr := NewIfdTagEntryValueResolver(rawExif, originalIndex.RootIfd.ByteOrder) rootIb := NewIfdBuilderFromExistingChain(originalIndex.RootIfd, itevr) updatedExif, err := ibe.EncodeToExif(rootIb) log.PanicIf(err) // Parse again. _, recoveredIndex, err := e.Collect(updatedExif) log.PanicIf(err) recoveredTags := recoveredIndex.RootIfd.DumpTags() recoveredThumbnailData, err := recoveredIndex.RootIfd.NextIfd.Thumbnail() log.PanicIf(err) // Check the thumbnail. if bytes.Compare(recoveredThumbnailData, originalThumbnailData) != 0 { t.Fatalf("recovered thumbnail does not match original") } // Validate that all of the same IFDs were presented. originalIfdTags := make([][2]interface{}, 0) for _, ite := range originalTags { if ite.ChildIfdName != "" { originalIfdTags = append(originalIfdTags, [2]interface{} { ite.Ii, ite.TagId }) } } recoveredIfdTags := make([][2]interface{}, 0) for _, ite := range recoveredTags { if ite.ChildIfdName != "" { recoveredIfdTags = append(recoveredIfdTags, [2]interface{} { ite.Ii, ite.TagId }) } } if reflect.DeepEqual(recoveredIfdTags, originalIfdTags) != true { fmt.Printf("Original IFD tags:\n\n") for i, x := range originalIfdTags { fmt.Printf(" %02d %v\n", i, x) } fmt.Printf("\nRecovered IFD tags:\n\n") for i, x := range recoveredIfdTags { fmt.Printf(" %02d %v\n", i, x) } fmt.Printf("\n") t.Fatalf("Recovered IFD tags are not correct.") } // Validate that all of the tags owned by the IFDs were presented. Note // that the thumbnail tags are not kept but only produced on the fly, which // is why we check it above. for i, recoveredIte := range recoveredTags { if recoveredIte.ChildIfdName != "" { continue } originalIte := originalTags[i] if recoveredIte.Ii != originalIte.Ii { t.Fatalf("IfdIdentify not as expected: %s != %s ITE=%s", recoveredIte.Ii, originalIte.Ii, recoveredIte) } else if recoveredIte.TagId != originalIte.TagId { t.Fatalf("Tag-ID not as expected: %s != %s ITE=%s", recoveredIte.TagId, originalIte.TagId, recoveredIte) } else if recoveredIte.TagType != originalIte.TagType { t.Fatalf("Tag-type not as expected: %d != %d ITE=%s", recoveredIte.TagType, originalIte.TagType, recoveredIte) } originalValueBytes, err := originalIte.ValueBytes(originalIndex.RootIfd.addressableData, originalIndex.RootIfd.ByteOrder) log.PanicIf(err) recoveredValueBytes, err := recoveredIte.ValueBytes(recoveredIndex.RootIfd.addressableData, recoveredIndex.RootIfd.ByteOrder) log.PanicIf(err) if bytes.Compare(originalValueBytes, recoveredValueBytes) != 0 { t.Fatalf("bytes of tag content not correct: %s != %s", originalIte, recoveredIte) } } } func Test_IfdBuilder_CreateIfdBuilderWithExistingIfd(t *testing.T) { tagId := IfdTagIdWithIdentityOrFail(GpsIi) parentIfd := &Ifd{ Ii: RootIi, Name: IfdStandard, } ifd := &Ifd{ Ii: GpsIi, Name: IfdGps, ByteOrder: TestDefaultByteOrder, Offset: 0x123, ParentIfd: parentIfd, } ib := NewIfdBuilderWithExistingIfd(ifd) if ib.ii.IfdName != ifd.Name { t.Fatalf("IFD-name not correct.") } else if ib.ifdTagId != tagId { t.Fatalf("IFD tag-ID not correct.") } else if ib.byteOrder != ifd.ByteOrder { t.Fatalf("IFD byte-order not correct.") } else if ib.existingOffset != ifd.Offset { t.Fatalf("IFD offset not correct.") } } func TestNewStandardBuilderTagFromConfig_OneUnit(t *testing.T) { bt := NewStandardBuilderTagFromConfig(ExifIi, uint16(0x8833), TestDefaultByteOrder, []uint32 { uint32(0x1234) }) if bt.ii != ExifIi { t.Fatalf("II in builderTag not correct") } else if bt.tagId != 0x8833 { t.Fatalf("tag-ID not correct") } else if bytes.Compare(bt.value.Bytes(), []byte { 0x0, 0x0, 0x12, 0x34, }) != 0 { t.Fatalf("value not correct") } } func TestNewStandardBuilderTagFromConfig_TwoUnits(t *testing.T) { bt := NewStandardBuilderTagFromConfig(ExifIi, uint16(0x8833), TestDefaultByteOrder, []uint32 { uint32(0x1234), uint32(0x5678) }) if bt.ii != ExifIi { t.Fatalf("II in builderTag not correct") } else if bt.tagId != 0x8833 { t.Fatalf("tag-ID not correct") } else if bytes.Compare(bt.value.Bytes(), []byte { 0x0, 0x0, 0x12, 0x34, 0x0, 0x0, 0x56, 0x78, }) != 0 { t.Fatalf("value not correct") } } func TestNewStandardBuilderTagFromConfigWithName(t *testing.T) { bt := NewStandardBuilderTagFromConfigWithName(ExifIi, "ISOSpeed", TestDefaultByteOrder, []uint32 { uint32(0x1234), uint32(0x5678) }) if bt.ii != ExifIi { t.Fatalf("II in builderTag not correct") } else if bt.tagId != 0x8833 { t.Fatalf("tag-ID not correct") } else if bytes.Compare(bt.value.Bytes(), []byte { 0x0, 0x0, 0x12, 0x34, 0x0, 0x0, 0x56, 0x78, }) != 0 { t.Fatalf("value not correct") } } func TestAddFromConfigWithName(t *testing.T) { ib := NewIfdBuilder(RootIi, TestDefaultByteOrder) err := ib.AddFromConfigWithName("ProcessingSoftware", "some software") log.PanicIf(err) if len(ib.tags) != 1 { t.Fatalf("Exactly one tag was not found: (%d)", len(ib.tags)) } bt := ib.tags[0] if bt.ii != RootIi { t.Fatalf("II not correct: %s", bt.ii) } else if bt.tagId != 0x000b { t.Fatalf("Tag-ID not correct: (0x%04x)", bt.tagId) } s := string(bt.value.Bytes()) if s != "some software\000" { t.Fatalf("Value not correct: (%d) [%s]", len(s), s) } }