icns

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

check_test.go (6033B)


      1 package main
      2 
      3 import (
      4 	"bytes"
      5 	"encoding/binary"
      6 	"image"
      7 	"image/color"
      8 	"image/png"
      9 	"os"
     10 	"strings"
     11 	"testing"
     12 
     13 	"github.com/jackmordaunt/icns/v4"
     14 	"github.com/jackmordaunt/icns/v4/ico"
     15 )
     16 
     17 func art(side int) image.Image {
     18 	img := image.NewNRGBA(image.Rect(0, 0, side, side))
     19 	for y := 0; y < side; y++ {
     20 		for x := 0; x < side; x++ {
     21 			img.SetNRGBA(x, y, color.NRGBA{
     22 				R: uint8(x * 255 / side),
     23 				G: uint8(y * 255 / side),
     24 				B: 0x40,
     25 				A: uint8(255 - y*128/side),
     26 			})
     27 		}
     28 	}
     29 	return img
     30 }
     31 
     32 func encoded(t *testing.T, write func(*bytes.Buffer) error) []byte {
     33 	t.Helper()
     34 	var buf bytes.Buffer
     35 	if err := write(&buf); err != nil {
     36 		t.Fatalf("encoding: %v", err)
     37 	}
     38 	return buf.Bytes()
     39 }
     40 
     41 // opaqueICO holds one PNG frame with no alpha channel, which Windows passes
     42 // over.
     43 func opaqueICO(t *testing.T) []byte {
     44 	t.Helper()
     45 	solid := image.NewNRGBA(image.Rect(0, 0, 256, 256))
     46 	for i := 0; i < len(solid.Pix); i += 4 {
     47 		solid.Pix[i], solid.Pix[i+1], solid.Pix[i+2], solid.Pix[i+3] = 9, 9, 9, 255
     48 	}
     49 	frame := encoded(t, func(b *bytes.Buffer) error { return png.Encode(b, solid) })
     50 	out := make([]byte, 0, 22+len(frame))
     51 	out = binary.LittleEndian.AppendUint16(out, 0)
     52 	out = binary.LittleEndian.AppendUint16(out, 1)
     53 	out = binary.LittleEndian.AppendUint16(out, 1)
     54 	out = append(out, 0, 0, 0, 0)
     55 	out = binary.LittleEndian.AppendUint16(out, 1)
     56 	out = binary.LittleEndian.AppendUint16(out, 32)
     57 	out = binary.LittleEndian.AppendUint32(out, uint32(len(frame)))
     58 	out = binary.LittleEndian.AppendUint32(out, 22)
     59 	return append(out, frame...)
     60 }
     61 
     62 // bundleICNS holds only the small PNG type that an app bundle will not
     63 // render.
     64 func bundleICNS(t *testing.T) []byte {
     65 	t.Helper()
     66 	frame := encoded(t, func(b *bytes.Buffer) error { return png.Encode(b, art(16)) })
     67 	element := func(id string, payload []byte) []byte {
     68 		out := append([]byte{}, id...)
     69 		out = binary.BigEndian.AppendUint32(out, uint32(8+len(payload)))
     70 		return append(out, payload...)
     71 	}
     72 	return element("icns", element("icp4", frame))
     73 }
     74 
     75 func TestCheckPassesOurOwnOutput(t *testing.T) {
     76 	for _, tt := range []struct {
     77 		name string
     78 		data []byte
     79 	}{
     80 		{"icns", encoded(t, func(b *bytes.Buffer) error { return icns.Encode(b, art(1024)) })},
     81 		{"ico", encoded(t, func(b *bytes.Buffer) error { return ico.Encode(b, art(256)) })},
     82 	} {
     83 		t.Run(tt.name, func(t *testing.T) {
     84 			if err := check("icon."+tt.name, bytes.NewReader(tt.data)); err != nil {
     85 				t.Errorf("check reported %v", err)
     86 			}
     87 		})
     88 	}
     89 }
     90 
     91 func TestCheckFailsOnFindingsThatShow(t *testing.T) {
     92 	for _, tt := range []struct {
     93 		name string
     94 		data []byte
     95 	}{
     96 		{"icon.ico", opaqueICO(t)},
     97 		{"icon.icns", bundleICNS(t)},
     98 	} {
     99 		t.Run(tt.name, func(t *testing.T) {
    100 			err := check(tt.name, bytes.NewReader(tt.data))
    101 			if err == nil {
    102 				t.Fatal("check reported nothing")
    103 			}
    104 			if !strings.Contains(err.Error(), "change what is drawn") {
    105 				t.Errorf("error is %v, want it to count the findings", err)
    106 			}
    107 			if !strings.Contains(err.Error(), tt.name) {
    108 				t.Errorf("error does not name the file: %v", err)
    109 			}
    110 		})
    111 	}
    112 }
    113 
    114 func TestCheckRejectsWhatIsNotAnIcon(t *testing.T) {
    115 	plain := encoded(t, func(b *bytes.Buffer) error { return png.Encode(b, art(64)) })
    116 	err := check("art.png", bytes.NewReader(plain))
    117 	if err == nil || !strings.Contains(err.Error(), "not an icns, ico or Windows binary") {
    118 		t.Errorf("check returned %v, want it to refuse a plain image", err)
    119 	}
    120 }
    121 
    122 // TestContainerReadsTheBytesFirst keeps a misnamed file from being validated
    123 // against the wrong format.
    124 func TestContainerReadsTheBytesFirst(t *testing.T) {
    125 	for _, tt := range []struct {
    126 		name string
    127 		data []byte
    128 		ext  string
    129 		want string
    130 	}{
    131 		{"icns named ico", encoded(t, func(b *bytes.Buffer) error { return icns.Encode(b, art(64)) }), ".ico", ".icns"},
    132 		{"ico named icns", encoded(t, func(b *bytes.Buffer) error { return ico.Encode(b, art(64)) }), ".icns", ".ico"},
    133 		{"unknown bytes, known extension", []byte("rubbish"), ".icns", ".icns"},
    134 		{"unknown bytes, plain extension", []byte("rubbish"), ".png", ""},
    135 		{"a binary named exe", []byte("MZ\x90\x00"), ".exe", ".exe"},
    136 		{"a binary named dll", []byte("MZ\x90\x00"), ".dll", ".dll"},
    137 		{"a binary named nothing", []byte("MZ\x90\x00"), "", ".exe"},
    138 	} {
    139 		t.Run(tt.name, func(t *testing.T) {
    140 			if got := container(tt.data, tt.ext); got != tt.want {
    141 				t.Errorf("container = %q, want %q", got, tt.want)
    142 			}
    143 		})
    144 	}
    145 }
    146 
    147 // TestCheckReadsABinary uses the exe package's fixture, a DLL whose resource
    148 // section mingw laid out, rather than building another one here.
    149 func TestCheckReadsABinary(t *testing.T) {
    150 	const fixture = "../../exe/testdata/icon.dll"
    151 	data, err := os.ReadFile(fixture)
    152 	if err != nil {
    153 		t.Fatalf("reading fixture: %v", err)
    154 	}
    155 	// The fixture holds 32, 24 and 16, so the only finding is the advice
    156 	// that larger sizes are absent.
    157 	if err := check("icon.dll", bytes.NewReader(data)); err != nil {
    158 		t.Errorf("check reported %v", err)
    159 	}
    160 }
    161 
    162 func TestCheckRefusesABinaryWithoutIcons(t *testing.T) {
    163 	data, err := os.ReadFile("../../exe/testdata/plain.dll")
    164 	if err != nil {
    165 		t.Fatalf("reading fixture: %v", err)
    166 	}
    167 	err = check("plain.dll", bytes.NewReader(data))
    168 	if err == nil || !strings.Contains(err.Error(), "no icons found") {
    169 		t.Errorf("check returned %v, want it to report no icons", err)
    170 	}
    171 }
    172 
    173 // TestWritableCoversEveryFormatTheUsageOffers keeps the usage text and what
    174 // the program will actually write from drifting apart.
    175 func TestWritableCoversEveryFormatTheUsageOffers(t *testing.T) {
    176 	for _, ext := range []string{".icns", ".icon", ".ico", ".png", ".jpg", ".jpeg"} {
    177 		if !writable(ext) {
    178 			t.Errorf("writable(%q) is false", ext)
    179 		}
    180 	}
    181 	for _, ext := range []string{".exe", ".dll", ".gif", ""} {
    182 		if writable(ext) {
    183 			t.Errorf("writable(%q) is true", ext)
    184 		}
    185 	}
    186 }
    187 
    188 func TestNameLabelsAPipe(t *testing.T) {
    189 	if got := name(""); got != "stdin" {
    190 		t.Errorf("name(\"\") = %q, want stdin", got)
    191 	}
    192 	if got := name("/tmp/art/icon.icns"); got != "icon.icns" {
    193 		t.Errorf("name = %q, want the base name", got)
    194 	}
    195 }