exe_test.go (5077B)
1 package exe 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "image/color" 8 "os" 9 "reflect" 10 "strings" 11 "testing" 12 ) 13 14 // The fixtures are built by mingw's windres and linker from an ico this 15 // module wrote, so the test runs against a resource section a Windows 16 // toolchain laid out rather than one written here. 17 // 18 // x86_64-w64-mingw32-windres icon.rc icon.o 19 // x86_64-w64-mingw32-gcc -shared -nostdlib -o icon.dll stub.c icon.o 20 const ( 21 withIcons = "testdata/icon.dll" 22 withoutIcons = "testdata/plain.dll" 23 original = "testdata/icon.ico" 24 ) 25 26 func open(t *testing.T, path string) *os.File { 27 t.Helper() 28 f, err := os.Open(path) 29 if err != nil { 30 t.Fatalf("opening fixture: %v", err) 31 } 32 t.Cleanup(func() { f.Close() }) 33 return f 34 } 35 36 func TestIconsFromABinary(t *testing.T) { 37 groups, err := Icons(open(t, withIcons)) 38 if err != nil { 39 t.Fatalf("reading icons: %v", err) 40 } 41 if len(groups) != 1 { 42 t.Fatalf("found %d groups, want 1", len(groups)) 43 } 44 group := groups[0] 45 if group.ID != 1 { 46 t.Errorf("group id is %d, want 1", group.ID) 47 } 48 if want := []int{32, 24, 16}; !reflect.DeepEqual(group.Sizes, want) { 49 t.Errorf("sizes are %v, want %v", group.Sizes, want) 50 } 51 } 52 53 // TestICOMatchesWhatWentIn is the point of the package: the resource section 54 // holds an ico taken apart, and putting it back together returns the file 55 // the linker was given. 56 func TestICOMatchesWhatWentIn(t *testing.T) { 57 groups, err := Icons(open(t, withIcons)) 58 if err != nil { 59 t.Fatalf("reading icons: %v", err) 60 } 61 want, err := os.ReadFile(original) 62 if err != nil { 63 t.Fatalf("reading the original: %v", err) 64 } 65 if got := groups[0].ICO(); !bytes.Equal(got, want) { 66 t.Errorf("reassembled %d bytes, want the %d that went in", len(got), len(want)) 67 } 68 } 69 70 func TestDecodeFromABinary(t *testing.T) { 71 img, err := Decode(open(t, withIcons)) 72 if err != nil { 73 t.Fatalf("decoding: %v", err) 74 } 75 if got := img.Bounds().Size(); got.X != 32 || got.Y != 32 { 76 t.Fatalf("decoded a %v icon, want the largest at 32x32", got) 77 } 78 // Each size was drawn in its own colour, so the wrong one would show. 79 c := color.NRGBAModel.Convert(img.At(16, 16)).(color.NRGBA) 80 if want := (color.NRGBA{R: 0x10, G: 0x20, B: 0xE0, A: 0xFF}); c != want { 81 t.Errorf("centre is %v, want the 32 pixel artwork %v", c, want) 82 } 83 } 84 85 func TestIconsWithoutResources(t *testing.T) { 86 _, err := Icons(open(t, withoutIcons)) 87 if !errors.Is(err, ErrNoIcons) { 88 t.Errorf("reading a binary with no icons returned %v, want ErrNoIcons", err) 89 } 90 } 91 92 func TestIconsRejectsWhatIsNotABinary(t *testing.T) { 93 if _, err := Icons(bytes.NewReader([]byte("not a binary"))); err == nil { 94 t.Error("reading rubbish returned no error") 95 } 96 } 97 98 // group builds a group icon directory naming the images given, so the error 99 // paths can be reached without a binary that holds them. 100 func group(id uint16, images ...uint16) leaf { 101 out := make([]byte, 0, groupHeaderSize+len(images)*groupEntrySize) 102 out = binary.LittleEndian.AppendUint16(out, 0) 103 out = binary.LittleEndian.AppendUint16(out, 1) 104 out = binary.LittleEndian.AppendUint16(out, uint16(len(images))) 105 for _, image := range images { 106 out = append(out, 32, 32, 0, 0) 107 out = binary.LittleEndian.AppendUint16(out, 1) 108 out = binary.LittleEndian.AppendUint16(out, 32) 109 out = binary.LittleEndian.AppendUint32(out, 16) 110 out = binary.LittleEndian.AppendUint16(out, image) 111 } 112 return leaf{id: id, data: out} 113 } 114 115 func TestAssembleRejectsAGroupItCannotComplete(t *testing.T) { 116 for _, tt := range []struct { 117 name string 118 group leaf 119 want string 120 }{ 121 { 122 name: "names an image that is absent", 123 group: group(1, 7), 124 want: "names image 7", 125 }, 126 { 127 name: "shorter than a header", 128 group: leaf{id: 1, data: []byte{0, 0}}, 129 want: "holds 2 bytes", 130 }, 131 { 132 name: "lists more icons than it holds", 133 group: leaf{id: 1, data: []byte{0, 0, 1, 0, 9, 0}}, 134 want: "lists 9 icons", 135 }, 136 } { 137 t.Run(tt.name, func(t *testing.T) { 138 _, err := assemble(tt.group, nil) 139 if err == nil { 140 t.Fatal("assembling returned no error") 141 } 142 if !errors.Is(err, ErrMalformed) { 143 t.Errorf("error is %v, want ErrMalformed", err) 144 } 145 if !strings.Contains(err.Error(), tt.want) { 146 t.Errorf("error is %v, want it to mention %q", err, tt.want) 147 } 148 }) 149 } 150 } 151 152 // TestAssembleTakesTheLengthFromTheResource keeps a directory that misstates 153 // a size from producing an ico nothing can read. 154 func TestAssembleTakesTheLengthFromTheResource(t *testing.T) { 155 // The directory above says every image is 16 bytes; this one is not. 156 pixels := bytes.Repeat([]byte{0xAB}, 64) 157 got, err := assemble(group(1, 3), []leaf{{id: 3, data: pixels}}) 158 if err != nil { 159 t.Fatalf("assembling: %v", err) 160 } 161 data := got.ICO() 162 row := data[groupHeaderSize:] 163 if size := binary.LittleEndian.Uint32(row[8:12]); int(size) != len(pixels) { 164 t.Errorf("row gives %d bytes, want the %d the resource holds", size, len(pixels)) 165 } 166 if offset := binary.LittleEndian.Uint32(row[12:16]); int(offset) != groupHeaderSize+icoEntrySize { 167 t.Errorf("row points at %d, want the byte after the directory", offset) 168 } 169 }