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