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