catinello.eu/x/cpuid@v0.0.0-20231214173555-81a76c018636/mockcpu_test.go (about) 1 package cpuid 2 3 import ( 4 "archive/zip" 5 "fmt" 6 "io/ioutil" 7 "math" 8 "path/filepath" 9 "sort" 10 "strings" 11 "testing" 12 ) 13 14 type fakecpuid map[uint32][][]uint32 15 16 type idfuncs struct { 17 cpuid func(op uint32) (eax, ebx, ecx, edx uint32) 18 cpuidex func(op, op2 uint32) (eax, ebx, ecx, edx uint32) 19 xgetbv func(index uint32) (eax, edx uint32) 20 } 21 22 func (f fakecpuid) String() string { 23 var out = make([]string, 0, len(f)) 24 for key, val := range f { 25 for _, v := range val { 26 out = append(out, fmt.Sprintf("CPUID %08x: [%08x, %08x, %08x, %08x]", key, v[0], v[1], v[2], v[3])) 27 } 28 } 29 sorter := sort.StringSlice(out) 30 sort.Sort(&sorter) 31 return strings.Join(sorter, "\n") 32 } 33 34 func mockCPU(def []byte) func() { 35 lines := strings.Split(string(def), "\n") 36 anyfound := false 37 fakeID := make(fakecpuid) 38 for _, line := range lines { 39 line = strings.Trim(line, "\r\t ") 40 if !strings.HasPrefix(line, "CPUID") { 41 continue 42 } 43 // Only collect for first cpu 44 if strings.HasPrefix(line, "CPUID 00000000") { 45 if anyfound { 46 break 47 } 48 } 49 //if !strings.Contains(line, "-") { 50 // continue 51 //} 52 items := strings.Split(line, ":") 53 if len(items) < 2 { 54 if len(line) == 51 || len(line) == 50 { 55 items = []string{line[0:14], line[15:]} 56 } else { 57 items = strings.Split(line, "\t") 58 if len(items) != 2 { 59 //fmt.Println("not found:", line, "len:", len(line)) 60 continue 61 } 62 } 63 } 64 items = items[0:2] 65 vals := strings.Trim(items[1], "\r\n ") 66 67 var idV uint32 68 n, err := fmt.Sscanf(items[0], "CPUID %x", &idV) 69 if err != nil || n != 1 { 70 continue 71 } 72 existing, ok := fakeID[idV] 73 if !ok { 74 existing = make([][]uint32, 0) 75 } 76 77 values := make([]uint32, 4) 78 n, err = fmt.Sscanf(vals, "%x-%x-%x-%x", &values[0], &values[1], &values[2], &values[3]) 79 if n != 4 || err != nil { 80 n, err = fmt.Sscanf(vals, "%x %x %x %x", &values[0], &values[1], &values[2], &values[3]) 81 if n != 4 || err != nil { 82 //fmt.Println("scanned", vals, "got", n, "Err:", err) 83 continue 84 } 85 } 86 87 existing = append(existing, values) 88 fakeID[idV] = existing 89 anyfound = true 90 } 91 92 restorer := func(f idfuncs) func() { 93 return func() { 94 cpuid = f.cpuid 95 cpuidex = f.cpuidex 96 xgetbv = f.xgetbv 97 } 98 }(idfuncs{cpuid: cpuid, cpuidex: cpuidex, xgetbv: xgetbv}) 99 100 cpuid = func(op uint32) (eax, ebx, ecx, edx uint32) { 101 if op == 0x80000000 || op == 0 { 102 var ok bool 103 _, ok = fakeID[op] 104 if !ok { 105 return 0, 0, 0, 0 106 } 107 } 108 first, ok := fakeID[op] 109 if !ok { 110 if op > maxFunctionID() { 111 panic(fmt.Sprintf("Base not found: %v, request:%#v\n", fakeID, op)) 112 } else { 113 // we have some entries missing 114 return 0, 0, 0, 0 115 } 116 } 117 theid := first[0] 118 return theid[0], theid[1], theid[2], theid[3] 119 } 120 cpuidex = func(op, op2 uint32) (eax, ebx, ecx, edx uint32) { 121 if op == 0x80000000 { 122 var ok bool 123 _, ok = fakeID[op] 124 if !ok { 125 return 0, 0, 0, 0 126 } 127 } 128 first, ok := fakeID[op] 129 if !ok { 130 if op > maxExtendedFunction() { 131 panic(fmt.Sprintf("Extended not found Info: %v, request:%#v, %#v\n", fakeID, op, op2)) 132 } else { 133 // we have some entries missing 134 return 0, 0, 0, 0 135 } 136 } 137 if int(op2) >= len(first) { 138 //fmt.Printf("Extended not found Info: %v, request:%#v, %#v\n", fakeID, op, op2) 139 return 0, 0, 0, 0 140 } 141 theid := first[op2] 142 return theid[0], theid[1], theid[2], theid[3] 143 } 144 xgetbv = func(index uint32) (eax, edx uint32) { 145 first, ok := fakeID[1] 146 if !ok { 147 panic(fmt.Sprintf("XGETBV not supported %v", fakeID)) 148 } 149 second := first[0] 150 // ECX bit 26 must be set 151 if (second[2] & 1 << 26) == 0 { 152 panic(fmt.Sprintf("XGETBV not supported %v", fakeID)) 153 } 154 // We don't have any data to return, unfortunately 155 return math.MaxUint32, math.MaxUint32 156 } 157 return restorer 158 } 159 160 func TestMocks(t *testing.T) { 161 zr, err := zip.OpenReader("testdata/cpuid_data.zip") 162 if err != nil { 163 t.Skip("No testdata:", err) 164 } 165 defer zr.Close() 166 for _, f := range zr.File { 167 t.Run(filepath.Base(f.Name), func(t *testing.T) { 168 rc, err := f.Open() 169 if err != nil { 170 t.Fatal(err) 171 } 172 content, err := ioutil.ReadAll(rc) 173 if err != nil { 174 t.Fatal(err) 175 } 176 rc.Close() 177 t.Log("Opening", f.FileInfo().Name()) 178 restore := mockCPU(content) 179 Detect() 180 t.Log("Name:", CPU.BrandName) 181 n := maxFunctionID() 182 t.Logf("Max Function:0x%x", n) 183 n = maxExtendedFunction() 184 t.Logf("Max Extended Function:0x%x", n) 185 t.Log("VendorString:", CPU.VendorString) 186 t.Log("VendorID:", CPU.VendorID) 187 t.Log("PhysicalCores:", CPU.PhysicalCores) 188 t.Log("ThreadsPerCore:", CPU.ThreadsPerCore) 189 t.Log("LogicalCores:", CPU.LogicalCores) 190 t.Log("Family", CPU.Family, "Model:", CPU.Model, "Stepping:", CPU.Stepping) 191 t.Log("Features:", strings.Join(CPU.FeatureSet(), ",")) 192 t.Log("Microarchitecture level:", CPU.X64Level()) 193 t.Log("Cacheline bytes:", CPU.CacheLine) 194 t.Log("L1 Instruction Cache:", CPU.Cache.L1I, "bytes") 195 t.Log("L1 Data Cache:", CPU.Cache.L1D, "bytes") 196 t.Log("L2 Cache:", CPU.Cache.L2, "bytes") 197 t.Log("L3 Cache:", CPU.Cache.L3, "bytes") 198 t.Log("Hz:", CPU.Hz, "Hz") 199 t.Log("Boost:", CPU.BoostFreq, "Hz") 200 if CPU.LogicalCores > 0 && CPU.PhysicalCores > 0 { 201 if CPU.LogicalCores != CPU.PhysicalCores*CPU.ThreadsPerCore { 202 t.Fatalf("Core count mismatch, LogicalCores (%d) != PhysicalCores (%d) * CPU.ThreadsPerCore (%d)", 203 CPU.LogicalCores, CPU.PhysicalCores, CPU.ThreadsPerCore) 204 } 205 } 206 207 if CPU.ThreadsPerCore > 1 && !CPU.Supports(HTT) { 208 t.Fatalf("Hyperthreading not detected") 209 } 210 if CPU.ThreadsPerCore == 1 && CPU.Supports(HTT) { 211 t.Fatalf("Hyperthreading detected, but only 1 Thread per core") 212 } 213 restore() 214 }) 215 } 216 217 Detect() 218 219 }