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