| // Copyright 2024 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package buildinfo | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "io" | |
| "testing" | |
| ) | |
| type byteExe struct { | |
| b []byte | |
| } | |
| func (x *byteExe) DataReader(addr uint64) (io.ReaderAt, error) { | |
| if addr >= uint64(len(x.b)) { | |
| return nil, fmt.Errorf("ReadData(%d) out of bounds of %d-byte slice", addr, len(x.b)) | |
| } | |
| return bytes.NewReader(x.b[addr:]), nil | |
| } | |
| func (x *byteExe) DataStart() (uint64, uint64) { | |
| return 0, uint64(len(x.b)) | |
| } | |
| func TestSearchMagic(t *testing.T) { | |
| tests := []struct { | |
| name string | |
| data []byte | |
| want uint64 | |
| wantErr error | |
| }{ | |
| { | |
| name: "beginning", | |
| data: func() []byte { | |
| b := make([]byte, buildInfoHeaderSize) | |
| copy(b, buildInfoMagic) | |
| return b | |
| }(), | |
| want: 0, | |
| }, | |
| { | |
| name: "offset", | |
| data: func() []byte { | |
| b := make([]byte, 512) | |
| copy(b[4*buildInfoAlign:], buildInfoMagic) | |
| return b | |
| }(), | |
| want: 4 * buildInfoAlign, | |
| }, | |
| { | |
| name: "second_chunk", | |
| data: func() []byte { | |
| b := make([]byte, 4*searchChunkSize) | |
| copy(b[searchChunkSize+4*buildInfoAlign:], buildInfoMagic) | |
| return b | |
| }(), | |
| want: searchChunkSize + 4*buildInfoAlign, | |
| }, | |
| { | |
| name: "second_chunk_short", | |
| data: func() []byte { | |
| // Magic is 64-bytes into the second chunk, | |
| // which is short; only exactly long enough to | |
| // hold the header. | |
| b := make([]byte, searchChunkSize+4*buildInfoAlign+buildInfoHeaderSize) | |
| copy(b[searchChunkSize+4*buildInfoAlign:], buildInfoMagic) | |
| return b | |
| }(), | |
| want: searchChunkSize + 4*buildInfoAlign, | |
| }, | |
| { | |
| name: "missing", | |
| data: func() []byte { | |
| b := make([]byte, buildInfoHeaderSize) | |
| return b | |
| }(), | |
| wantErr: errNotGoExe, | |
| }, | |
| { | |
| name: "too_short", | |
| data: func() []byte { | |
| // There needs to be space for the entire | |
| // header, not just the magic. | |
| b := make([]byte, len(buildInfoMagic)) | |
| copy(b, buildInfoMagic) | |
| return b | |
| }(), | |
| wantErr: errNotGoExe, | |
| }, | |
| { | |
| name: "misaligned", | |
| data: func() []byte { | |
| b := make([]byte, 512) | |
| copy(b[7:], buildInfoMagic) | |
| return b | |
| }(), | |
| wantErr: errNotGoExe, | |
| }, | |
| { | |
| name: "misaligned_across_chunk", | |
| data: func() []byte { | |
| // Magic crosses chunk boundary. By definition, | |
| // it has to be misaligned. | |
| b := make([]byte, 2*searchChunkSize) | |
| copy(b[searchChunkSize-8:], buildInfoMagic) | |
| return b | |
| }(), | |
| wantErr: errNotGoExe, | |
| }, | |
| { | |
| name: "header_across_chunk", | |
| data: func() []byte { | |
| // The magic is aligned within the first chunk, | |
| // but the rest of the 32-byte header crosses | |
| // the chunk boundary. | |
| b := make([]byte, 2*searchChunkSize) | |
| copy(b[searchChunkSize-buildInfoAlign:], buildInfoMagic) | |
| return b | |
| }(), | |
| want: searchChunkSize - buildInfoAlign, | |
| }, | |
| } | |
| for _, tc := range tests { | |
| t.Run(tc.name, func(t *testing.T) { | |
| x := &byteExe{tc.data} | |
| dataAddr, dataSize := x.DataStart() | |
| addr, err := searchMagic(x, dataAddr, dataSize) | |
| if tc.wantErr == nil { | |
| if err != nil { | |
| t.Errorf("searchMagic got err %v want nil", err) | |
| } | |
| if addr != tc.want { | |
| t.Errorf("searchMagic got addr %d want %d", addr, tc.want) | |
| } | |
| } else { | |
| if err != tc.wantErr { | |
| t.Errorf("searchMagic got err %v want %v", err, tc.wantErr) | |
| } | |
| } | |
| }) | |
| } | |
| } |