icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

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 }