git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/cpuinfo/mockcpu_test.go (about)

     1  package cpuinfo
     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  }