oracle_windows_test.go (4818B)
1 //go:build windows 2 3 package ico 4 5 // Windows decides what an ico file may contain, so these tests hand it files 6 // this package wrote and read back a file it wrote itself. They run wherever 7 // Windows PowerShell is present, which covers the Windows CI runner. 8 9 import ( 10 "image" 11 "image/color" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "strconv" 16 "strings" 17 "testing" 18 ) 19 20 // readFrames lists every frame the Windows Imaging Component finds, as 21 // "width height r g b a". This is the decoder behind Explorer's preview. 22 const readFrames = ` 23 param([string]$Path) 24 Add-Type -AssemblyName PresentationCore 25 $stream = [System.IO.File]::OpenRead($Path) 26 $decoder = [System.Windows.Media.Imaging.BitmapDecoder]::Create($stream, 'None', 'OnLoad') 27 foreach ($frame in $decoder.Frames) { 28 $converted = New-Object System.Windows.Media.Imaging.FormatConvertedBitmap($frame, [System.Windows.Media.PixelFormats]::Bgra32, $null, 0) 29 $pixel = New-Object byte[] 4 30 $at = New-Object System.Windows.Int32Rect ([int]($converted.PixelWidth/2)), ([int]($converted.PixelHeight/2)), 1, 1 31 $converted.CopyPixels($at, $pixel, 4, 0) 32 "{0} {1} {2} {3} {4} {5}" -f $converted.PixelWidth, $converted.PixelHeight, $pixel[2], $pixel[1], $pixel[0], $pixel[3] 33 } 34 $stream.Close() 35 ` 36 37 // writeIcon has Windows write an ico file of one solid colour. 38 const writeIcon = ` 39 param([string]$Path, [int]$Size, [int]$R, [int]$G, [int]$B) 40 Add-Type -AssemblyName System.Drawing 41 $bitmap = New-Object System.Drawing.Bitmap($Size, $Size, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb) 42 $graphics = [System.Drawing.Graphics]::FromImage($bitmap) 43 $graphics.Clear([System.Drawing.Color]::FromArgb(255, $R, $G, $B)) 44 $graphics.Dispose() 45 $icon = [System.Drawing.Icon]::FromHandle($bitmap.GetHicon()) 46 $stream = [System.IO.File]::Create($Path) 47 $icon.Save($stream) 48 $stream.Close() 49 ` 50 51 // powershell runs a script and returns what it printed, failing the test 52 // with whatever it wrote if it did not run. 53 func powershell(t *testing.T, script string, args ...string) string { 54 t.Helper() 55 path := filepath.Join(t.TempDir(), "oracle.ps1") 56 if err := os.WriteFile(path, []byte(script), 0o600); err != nil { 57 t.Fatal(err) 58 } 59 args = append([]string{"-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path}, args...) 60 out, err := exec.Command("powershell.exe", args...).CombinedOutput() 61 if err != nil { 62 t.Fatalf("powershell %v: %v\n%s", args, err, out) 63 } 64 return string(out) 65 } 66 67 // TestOracleWindowsReads checks that Windows finds every icon this package 68 // writes, at the size and colour it was given. 69 func TestOracleWindowsReads(t *testing.T) { 70 var ( 71 images = map[uint]image.Image{} 72 want = map[int]color.NRGBA{} 73 ) 74 for i, size := range Sizes() { 75 c := color.NRGBA{R: uint8(20 + i*30), G: uint8(200 - i*20), B: 0x40, A: 0xFF} 76 images[size] = solid(int(size), c) 77 want[int(size)] = c 78 } 79 path := filepath.Join(t.TempDir(), "icon.ico") 80 f, err := os.Create(path) 81 if err != nil { 82 t.Fatal(err) 83 } 84 if err := NewEncoder(f).EncodeSizes(images); err != nil { 85 f.Close() 86 t.Fatal(err) 87 } 88 if err := f.Close(); err != nil { 89 t.Fatal(err) 90 } 91 92 out := powershell(t, readFrames, "-Path", path) 93 seen := map[int]bool{} 94 for _, line := range strings.Split(out, "\n") { 95 fields := strings.Fields(line) 96 if len(fields) != 6 { 97 continue 98 } 99 n := make([]int, 6) 100 for i, field := range fields { 101 if n[i], err = strconv.Atoi(field); err != nil { 102 t.Fatalf("the oracle printed %q", line) 103 } 104 } 105 width, height := n[0], n[1] 106 if width != height { 107 t.Errorf("Windows read a %dx%d icon, which is not square", width, height) 108 } 109 got := color.NRGBA{R: uint8(n[2]), G: uint8(n[3]), B: uint8(n[4]), A: uint8(n[5])} 110 if got != want[width] { 111 t.Errorf("Windows read the %d pixel icon as %v, want %v", width, got, want[width]) 112 } 113 seen[width] = true 114 } 115 for size := range want { 116 if !seen[size] { 117 t.Errorf("Windows did not read the %d pixel icon at all:\n%s", size, out) 118 } 119 } 120 } 121 122 // TestOracleWindowsWrites decodes an icon Windows wrote. It writes them 123 // indexed, so this covers the colour table against a real writer. 124 func TestOracleWindowsWrites(t *testing.T) { 125 const size = 48 126 // Red is in the palette Windows picks, so the colour survives exactly. 127 want := color.NRGBA{R: 0xFF, A: 0xFF} 128 path := filepath.Join(t.TempDir(), "windows.ico") 129 powershell(t, writeIcon, 130 "-Path", path, 131 "-Size", strconv.Itoa(size), 132 "-R", strconv.Itoa(int(want.R)), 133 "-G", strconv.Itoa(int(want.G)), 134 "-B", strconv.Itoa(int(want.B)), 135 ) 136 f, err := os.Open(path) 137 if err != nil { 138 t.Fatal(err) 139 } 140 defer f.Close() 141 img, err := Decode(f) 142 if err != nil { 143 t.Fatal(err) 144 } 145 if got := img.Bounds().Dx(); got != size { 146 t.Errorf("decoded a %d pixel icon, want %d", got, size) 147 } 148 if got := centre(img); got != want { 149 t.Errorf("centre = %v, want %v", got, want) 150 } 151 }