github.com/ubuntu/ubuntu-report@v1.7.4-0.20240410144652-96f37d845fac/internal/metrics/internals_test.go (about) 1 package metrics 2 3 import ( 4 "context" 5 "flag" 6 "os/exec" 7 "path" 8 "path/filepath" 9 "testing" 10 11 "github.com/ubuntu/ubuntu-report/internal/helper" 12 ) 13 14 /* 15 * Tests here some private functions to gather metrics 16 * Collect() public API is calling out a lot of functions, 17 * that's why we add some unit tests on direct Collect() callees here 18 * for finer-graind results in case of failure. 19 */ 20 21 var Update = flag.Bool("update", false, "update golden files") 22 23 func TestInstallerInfo(t *testing.T) { 24 t.Parallel() 25 26 testCases := []struct { 27 name string 28 root string 29 }{ 30 {"regular", "testdata/good"}, 31 {"empty file", "testdata/empty"}, 32 {"doesn't exist", "testdata/none"}, 33 {"garbage content", "testdata/garbage"}, 34 } 35 for _, tc := range testCases { 36 tc := tc // capture range variable for parallel execution 37 t.Run(tc.name, func(t *testing.T) { 38 t.Parallel() 39 a := helper.Asserter{T: t} 40 41 m := newTestMetrics(t, WithRootAt(tc.root)) 42 got := []byte(m.installerInfo()) 43 want := helper.LoadOrUpdateGolden(t, path.Join(m.root, "gold", "intallerInfo"), got, *Update) 44 45 a.Equal(got, want) 46 }) 47 } 48 } 49 50 func TestUpgradeInfo(t *testing.T) { 51 t.Parallel() 52 53 testCases := []struct { 54 name string 55 root string 56 }{ 57 {"regular", "testdata/good"}, 58 {"empty file", "testdata/empty"}, 59 {"doesn't exist", "testdata/none"}, 60 {"garbage content", "testdata/garbage"}, 61 } 62 for _, tc := range testCases { 63 tc := tc // capture range variable for parallel execution 64 t.Run(tc.name, func(t *testing.T) { 65 t.Parallel() 66 a := helper.Asserter{T: t} 67 68 m := newTestMetrics(t, WithRootAt(tc.root)) 69 got := []byte(m.upgradeInfo()) 70 want := helper.LoadOrUpdateGolden(t, filepath.Join(m.root, "gold", "upgradeInfo"), got, *Update) 71 72 a.Equal(got, want) 73 }) 74 } 75 } 76 77 func TestGetVersion(t *testing.T) { 78 t.Parallel() 79 80 testCases := []struct { 81 name string 82 root string 83 84 want string 85 }{ 86 {"regular", "testdata/good", "18.04"}, 87 {"empty file", "testdata/empty", ""}, 88 {"missing", "testdata/missing-fields/ids/version", ""}, 89 {"empty", "testdata/empty-fields/ids/version", ""}, 90 {"doesn't exist", "testdata/none", ""}, 91 {"garbage content", "testdata/garbage", ""}, 92 } 93 for _, tc := range testCases { 94 tc := tc // capture range variable for parallel execution 95 t.Run(tc.name, func(t *testing.T) { 96 t.Parallel() 97 a := helper.Asserter{T: t} 98 99 m := newTestMetrics(t, WithRootAt(tc.root)) 100 got := m.getVersion() 101 102 a.Equal(got, tc.want) 103 }) 104 } 105 } 106 107 func TestGetRAM(t *testing.T) { 108 t.Parallel() 109 110 normalRAM := 8.0 111 testCases := []struct { 112 name string 113 root string 114 115 want *float64 116 }{ 117 {"regular", "testdata/good", &normalRAM}, 118 {"empty file", "testdata/empty", nil}, 119 {"missing", "testdata/missing-fields/ram", nil}, 120 {"empty", "testdata/empty-fields/ram", nil}, 121 {"malformed", "testdata/specials/ram/malformed", nil}, 122 {"doesn't exist", "testdata/none", nil}, 123 {"garbage content", "testdata/garbage", nil}, 124 } 125 for _, tc := range testCases { 126 tc := tc // capture range variable for parallel execution 127 t.Run(tc.name, func(t *testing.T) { 128 t.Parallel() 129 a := helper.Asserter{T: t} 130 131 m := newTestMetrics(t, WithRootAt(tc.root)) 132 got := m.getRAM() 133 134 a.Equal(got, tc.want) 135 }) 136 } 137 } 138 139 func TestGetTimeZone(t *testing.T) { 140 t.Parallel() 141 142 testCases := []struct { 143 name string 144 root string 145 146 want string 147 }{ 148 {"regular", "testdata/good", "Europe/Paris"}, 149 {"empty file", "testdata/empty", ""}, 150 {"doesn't exist", "testdata/none", ""}, 151 {"garbage content", "testdata/garbage", ""}, 152 } 153 for _, tc := range testCases { 154 tc := tc // capture range variable for parallel execution 155 t.Run(tc.name, func(t *testing.T) { 156 t.Parallel() 157 a := helper.Asserter{T: t} 158 159 m := newTestMetrics(t, WithRootAt(tc.root)) 160 got := m.getTimeZone() 161 162 a.Equal(got, tc.want) 163 }) 164 } 165 } 166 167 func TestGetAutologin(t *testing.T) { 168 t.Parallel() 169 170 testCases := []struct { 171 name string 172 root string 173 174 want bool 175 }{ 176 {"regular", "testdata/good", false}, 177 {"empty file", "testdata/empty", false}, 178 {"missing", "testdata/missing-fields/autologin", false}, 179 {"empty", "testdata/empty-fields/autologin", false}, 180 {"enabled", "testdata/specials/autologin/true", true}, 181 {"disabled", "testdata/specials/autologin/false", false}, 182 {"enabled no space", "testdata/specials/autologin/true-no-space", true}, 183 {"uppercase", "testdata/specials/autologin/true-uppercase", true}, 184 {"doesn't exist", "testdata/none", false}, 185 {"garbage content", "testdata/garbage", false}, 186 } 187 for _, tc := range testCases { 188 tc := tc // capture range variable for parallel execution 189 t.Run(tc.name, func(t *testing.T) { 190 t.Parallel() 191 a := helper.Asserter{T: t} 192 193 m := newTestMetrics(t, WithRootAt(tc.root)) 194 got := m.getAutologin() 195 196 a.Equal(got, tc.want) 197 }) 198 } 199 } 200 201 func TestGetOEM(t *testing.T) { 202 t.Parallel() 203 204 testCases := []struct { 205 name string 206 root string 207 208 wantVendor string 209 wantProduct string 210 wantFamily string 211 wantDCD string 212 }{ 213 {"regular", "testdata/good", "DID", "4287CTO", "Thinkpad", ""}, 214 {"with dcd", "testdata/specials/oem/with-dcd", "", "", "", "canonical-oem-somerville-xenial-amd64-20160624-2"}, 215 {"empty vendor", "testdata/empty-fields/oem/vendor", "", "4287CTO", "Thinkpad", ""}, 216 {"empty product", "testdata/empty-fields/oem/product", "DID", "", "Thinkpad", ""}, 217 {"empty family", "testdata/empty-fields/oem/family", "DID", "4287CTO", "", ""}, 218 {"empty dcd", "testdata/empty-fields/oem/dcd", "DID", "4287CTO", "Thinkpad", ""}, 219 {"empty both", "testdata/empty", "", "", "", ""}, 220 {"doesn't exist", "testdata/none", "", "", "", ""}, 221 {"garbage content", "testdata/garbage", "", "", "", ""}, 222 } 223 for _, tc := range testCases { 224 tc := tc // capture range variable for parallel execution 225 t.Run(tc.name, func(t *testing.T) { 226 t.Parallel() 227 a := helper.Asserter{T: t} 228 229 m := newTestMetrics(t, WithRootAt(tc.root)) 230 vendor, product, family, dcd := m.getOEM() 231 232 a.Equal(vendor, tc.wantVendor) 233 a.Equal(product, tc.wantProduct) 234 a.Equal(family, tc.wantFamily) 235 a.Equal(dcd, tc.wantDCD) 236 }) 237 } 238 } 239 240 func TestGetBIOS(t *testing.T) { 241 t.Parallel() 242 243 testCases := []struct { 244 name string 245 root string 246 247 wantVendor string 248 wantVersion string 249 }{ 250 {"regular", "testdata/good", "DID", "42 (maybe 43)"}, 251 {"empty vendor", "testdata/empty-fields/bios/vendor", "", "42 (maybe 43)"}, 252 {"empty product", "testdata/empty-fields/bios/version", "DID", ""}, 253 {"empty both", "testdata/empty", "", ""}, 254 {"doesn't exist", "testdata/none", "", ""}, 255 {"garbage content", "testdata/garbage", "", ""}, 256 } 257 for _, tc := range testCases { 258 tc := tc // capture range variable for parallel execution 259 t.Run(tc.name, func(t *testing.T) { 260 t.Parallel() 261 a := helper.Asserter{T: t} 262 263 m := newTestMetrics(t, WithRootAt(tc.root)) 264 vendor, version := m.getBIOS() 265 266 a.Equal(vendor, tc.wantVendor) 267 a.Equal(version, tc.wantVersion) 268 }) 269 } 270 } 271 272 func TestGetLivePatch(t *testing.T) { 273 t.Parallel() 274 275 testCases := []struct { 276 name string 277 root string 278 279 want bool 280 }{ 281 {"regular", "testdata/good", true}, 282 {"disabled", "testdata/none", false}, 283 } 284 for _, tc := range testCases { 285 tc := tc // capture range variable for parallel execution 286 t.Run(tc.name, func(t *testing.T) { 287 t.Parallel() 288 a := helper.Asserter{T: t} 289 290 m := newTestMetrics(t, WithRootAt(tc.root)) 291 enabled := m.getLivePatch() 292 293 a.Equal(enabled, tc.want) 294 }) 295 } 296 } 297 298 func TestGetDisks(t *testing.T) { 299 t.Parallel() 300 301 testCases := []struct { 302 name string 303 root string 304 305 wantSize []float64 306 }{ 307 {"one disk", "testdata/good", []float64{240.1}}, 308 {"multiple disks", "testdata/specials/disks/multiple", []float64{240.1, 500.1}}, 309 {"all supported formats", "testdata/specials/disks/all-formats", []float64{500.1, 240.1, 192.9}}, 310 {"no disks", "testdata/specials/disks/no-disks", nil}, 311 {"filters undesired devices", "testdata/specials/disks/filter-devices", []float64{240.1}}, 312 {"no block numbers", "testdata/empty-fields/disks/block-numbers", nil}, 313 {"no block logical size", "testdata/empty-fields/disks/block-logical-size", nil}, 314 {"none", "testdata/none", nil}, 315 {"garbage block numbers", "testdata/specials/disks/garbage-block-numbers", nil}, 316 {"garbage block size", "testdata/specials/disks/garbage-block-size", nil}, 317 } 318 for _, tc := range testCases { 319 tc := tc // capture range variable for parallel execution 320 t.Run(tc.name, func(t *testing.T) { 321 t.Parallel() 322 a := helper.Asserter{T: t} 323 324 m := newTestMetrics(t, WithRootAt(tc.root)) 325 disks := m.getDisks() 326 327 a.Equal(disks, tc.wantSize) 328 }) 329 } 330 } 331 func TestGetCPU(t *testing.T) { 332 t.Parallel() 333 334 testCases := []struct { 335 name string 336 337 want cpuInfo 338 }{ 339 {"regular", cpuInfo{"32-bit, 64-bit", "8", "2", "4", "1", "Genuine", "6", "158", "10", 340 "Intuis Corus i5-8300H CPU @ 2.30GHz", "VT-x", "", ""}}, 341 {"missing one expected field", cpuInfo{"32-bit, 64-bit", "8", "2", "4", "1", "", "6", "158", "10", 342 "Intuis Corus i5-8300H CPU @ 2.30GHz", "VT-x", "", ""}}, 343 {"missing one optional field", cpuInfo{"32-bit, 64-bit", "8", "2", "4", "1", "Genuine", "6", "158", "10", 344 "Intuis Corus i5-8300H CPU @ 2.30GHz", "VT-x", "", ""}}, 345 {"virtualized", cpuInfo{"32-bit, 64-bit", "8", "2", "4", "1", "Genuine", "6", "158", "10", 346 "Intuis Corus i5-8300H CPU @ 2.30GHz", "VT-x", "KVM", "full"}}, 347 {"without space", cpuInfo{"32-bit, 64-bit", "8", "2", "4", "1", "Genuine", "6", "158", "10", 348 "Intuis Corus i5-8300H CPU @ 2.30GHz", "VT-x", "", ""}}, 349 {"empty", cpuInfo{}}, 350 {"garbage", cpuInfo{}}, 351 {"fail", cpuInfo{}}, 352 } 353 for _, tc := range testCases { 354 tc := tc // capture range variable for parallel execution 355 t.Run(tc.name, func(t *testing.T) { 356 t.Parallel() 357 a := helper.Asserter{T: t} 358 359 cmd, cancel := newMockShortCmd(t, "lscpu", "-J", tc.name) 360 defer cancel() 361 362 m := newTestMetrics(t, WithCPUInfoCommand(cmd)) 363 info := m.getCPU() 364 365 a.Equal(info, tc.want) 366 }) 367 } 368 } 369 370 func TestGetGPU(t *testing.T) { 371 t.Parallel() 372 373 testCases := []struct { 374 name string 375 376 want []gpuInfo 377 }{ 378 {"one gpu", []gpuInfo{{"8086", "0126"}}}, 379 {"multiple gpus", []gpuInfo{{"8086", "0126"}, {"8086", "0127"}}}, 380 {"no revision number", []gpuInfo{{"8086", "0126"}}}, 381 {"no gpu", nil}, 382 {"hexa numbers", []gpuInfo{{"8b86", "a126"}}}, 383 {"empty", nil}, 384 {"malformed gpu line", nil}, 385 {"garbage", nil}, 386 {"fail", nil}, 387 } 388 for _, tc := range testCases { 389 tc := tc // capture range variable for parallel execution 390 t.Run(tc.name, func(t *testing.T) { 391 t.Parallel() 392 a := helper.Asserter{T: t} 393 394 cmd, cancel := newMockShortCmd(t, "lspci", "-n", tc.name) 395 defer cancel() 396 397 m := newTestMetrics(t, WithGPUInfoCommand(cmd)) 398 info := m.getGPU() 399 400 a.Equal(info, tc.want) 401 }) 402 } 403 } 404 405 func TestGetScreens(t *testing.T) { 406 t.Parallel() 407 408 testCases := []struct { 409 name string 410 411 want []screenInfo 412 }{ 413 {"one screen", []screenInfo{{"277mmx156mm", "1366x768", "60.02"}}}, 414 {"multiple screens", []screenInfo{{"277mmx156mm", "1366x768", "60.02"}, {"510mmx287mm", "1920x1080", "60.00"}}}, 415 {"no screen", nil}, 416 {"chosen resolution not first", []screenInfo{{"510mmx287mm", "1600x1200", "60.00"}}}, 417 {"no specified screen size", nil}, 418 {"no chosen resolution", nil}, 419 {"chosen resolution not preferred", []screenInfo{{"510mmx287mm", "1920x1080", "60.00"}}}, 420 {"multiple frequencies for resolution", []screenInfo{{"510mmx287mm", "1920x1080", "60.00"}}}, 421 {"multiple frequencies select other resolution", []screenInfo{{"510mmx287mm", "1920x1080", "50.00"}}}, 422 {"multiple frequencies select other resolution on non preferred", []screenInfo{{"510mmx287mm", "1920x1080", "50.00"}}}, 423 {"empty", nil}, 424 {"malformed screen line", nil}, 425 {"garbage", nil}, 426 {"fail", nil}, 427 } 428 for _, tc := range testCases { 429 tc := tc // capture range variable for parallel execution 430 t.Run(tc.name, func(t *testing.T) { 431 t.Parallel() 432 a := helper.Asserter{T: t} 433 434 cmd, cancel := newMockShortCmd(t, "xrandr", tc.name) 435 defer cancel() 436 437 m := newTestMetrics(t, WithScreenInfoCommand(cmd)) 438 info := m.getScreens() 439 440 a.Equal(info, tc.want) 441 }) 442 } 443 } 444 445 func TestGetPartitions(t *testing.T) { 446 t.Parallel() 447 448 testCases := []struct { 449 name string 450 451 want []float64 452 }{ 453 {"one partition", []float64{159.4}}, 454 {"multiple partitions", []float64{159.4, 309.7}}, 455 {"no partitions", nil}, 456 {"filters loop devices", []float64{159.4}}, 457 {"empty", nil}, 458 {"malformed partition line string", nil}, 459 {"malformed partition line one field", nil}, 460 {"garbage", nil}, 461 {"fail", nil}, 462 } 463 for _, tc := range testCases { 464 tc := tc // capture range variable for parallel execution 465 t.Run(tc.name, func(t *testing.T) { 466 t.Parallel() 467 a := helper.Asserter{T: t} 468 469 cmd, cancel := newMockShortCmd(t, "df", tc.name) 470 defer cancel() 471 472 m := newTestMetrics(t, WithSpaceInfoCommand(cmd)) 473 info := m.getPartitions() 474 475 a.Equal(info, tc.want) 476 }) 477 } 478 } 479 480 func TestGetArch(t *testing.T) { 481 t.Parallel() 482 483 testCases := []struct { 484 name string 485 486 want string 487 }{ 488 {"regular", "amd64"}, 489 {"empty", ""}, 490 {"fail", ""}, 491 } 492 for _, tc := range testCases { 493 tc := tc // capture range variable for parallel execution 494 t.Run(tc.name, func(t *testing.T) { 495 t.Parallel() 496 a := helper.Asserter{T: t} 497 498 cmd, cancel := newMockShortCmd(t, "dpkg", "--print-architecture", tc.name) 499 defer cancel() 500 501 m := newTestMetrics(t, WithArchitectureCommand(cmd)) 502 arch := m.getArch() 503 504 a.Equal(arch, tc.want) 505 }) 506 } 507 } 508 509 func TestGetHwCap(t *testing.T) { 510 t.Parallel() 511 512 testCases := []struct { 513 name string 514 515 want string 516 }{ 517 {"regular", "x86-64-v3"}, 518 {"no hwcap", "-"}, 519 {"empty", ""}, 520 {"fail", ""}, 521 } 522 for _, tc := range testCases { 523 tc := tc // capture range variable for parallel execution 524 t.Run(tc.name, func(t *testing.T) { 525 t.Parallel() 526 a := helper.Asserter{T: t} 527 528 hwCapCmd, cancel := newMockShortCmd(t, "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2", "--help", tc.name) 529 defer cancel() 530 531 m := newTestMetrics(t, WithHwCapCommand(hwCapCmd)) 532 hwCap := m.getHwCap() 533 534 a.Equal(hwCap, tc.want) 535 }) 536 } 537 } 538 539 func TestGetLibc6Ver(t *testing.T) { 540 t.Parallel() 541 542 testCases := []struct { 543 name string 544 545 want string 546 }{ 547 {"old version", ""}, 548 {"not installed", ""}, 549 } 550 for _, tc := range testCases { 551 tc := tc // capture range variable for parallel execution 552 t.Run(tc.name, func(t *testing.T) { 553 t.Parallel() 554 a := helper.Asserter{T: t} 555 556 libc6Cmd, cancel := newMockShortCmd(t, "dpkg", "--status", "libc6", tc.name) 557 defer cancel() 558 559 m := newTestMetrics(t, WithLibc6Command(libc6Cmd)) 560 hwCap := m.getHwCap() 561 562 a.Equal(hwCap, tc.want) 563 }) 564 } 565 } 566 567 func TestGetLanguage(t *testing.T) { 568 t.Parallel() 569 570 testCases := []struct { 571 name string 572 env map[string]string 573 574 want string 575 }{ 576 {"regular", map[string]string{"LANG": "fr_FR.UTF-8", "LANGUAGE": "fr_FR.UTF-8"}, "fr_FR"}, 577 {"LC_ALL override all", map[string]string{ 578 "LC_ALL": "en_US.UTF-8", "LANG": "fr_FR.UTF-8", "LANGUAGE": "fr_FR.UTF-8"}, "en_US"}, 579 {"LANG override LANGUAGE", 580 map[string]string{"LANG": "en_US.UTF-8", "LANGUAGE": "fr_FR.UTF-8"}, "en_US"}, 581 {"LANGUAGE only", 582 map[string]string{"LANGUAGE": "fr_FR.UTF-8"}, "fr_FR"}, 583 {"only first in LANGUAGE list", 584 map[string]string{"LANGUAGE": "fr_FR.UTF-8:en_US.UTF8"}, "fr_FR"}, 585 {"without encoding", map[string]string{"LANG": "fr_FR", "LANGUAGE": "fr_FR"}, "fr_FR"}, 586 {"none", nil, ""}, 587 } 588 for _, tc := range testCases { 589 tc := tc // capture range variable for parallel execution 590 t.Run(tc.name, func(t *testing.T) { 591 t.Parallel() 592 a := helper.Asserter{T: t} 593 594 m := newTestMetrics(t, WithMapForEnv(tc.env)) 595 got := m.getLanguage() 596 597 a.Equal(got, tc.want) 598 }) 599 } 600 601 } 602 603 func newTestMetrics(t *testing.T, fixtures ...func(m *Metrics) error) Metrics { 604 t.Helper() 605 m, err := New(fixtures...) 606 if err != nil { 607 t.Fatal("can't create metrics object", err) 608 } 609 return m 610 } 611 612 func newMockShortCmd(t *testing.T, s ...string) (*exec.Cmd, context.CancelFunc) { 613 t.Helper() 614 return helper.ShortProcess(t, "TestMetricsHelperProcess", s...) 615 }