github.com/vlifesystems/rulehunter@v0.0.0-20180501090014-673078aa4a83/report/report_test.go (about) 1 package report 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "math" 8 "os" 9 "path/filepath" 10 "reflect" 11 "syscall" 12 "testing" 13 "time" 14 15 "github.com/lawrencewoodman/dlit" 16 "github.com/vlifesystems/rhkit/aggregator" 17 rhkassessment "github.com/vlifesystems/rhkit/assessment" 18 "github.com/vlifesystems/rhkit/description" 19 "github.com/vlifesystems/rhkit/goal" 20 "github.com/vlifesystems/rhkit/rule" 21 "github.com/vlifesystems/rulehunter/config" 22 "github.com/vlifesystems/rulehunter/internal" 23 "github.com/vlifesystems/rulehunter/internal/testhelpers" 24 ) 25 26 var testDescription = &description.Description{ 27 map[string]*description.Field{ 28 "month": {description.String, nil, nil, 0, 29 map[string]description.Value{ 30 "feb": {dlit.MustNew("feb"), 3}, 31 "may": {dlit.MustNew("may"), 2}, 32 "june": {dlit.MustNew("june"), 9}, 33 }, 34 3, 35 }, 36 "rate": { 37 description.Number, 38 dlit.MustNew(0.3), 39 dlit.MustNew(15.1), 40 3, 41 map[string]description.Value{ 42 "0.3": {dlit.MustNew(0.3), 7}, 43 "7": {dlit.MustNew(7), 2}, 44 "7.3": {dlit.MustNew(7.3), 9}, 45 "9.278": {dlit.MustNew(9.278), 4}, 46 }, 47 4, 48 }, 49 "method": {description.Ignore, nil, nil, 0, 50 map[string]description.Value{}, -1}, 51 }, 52 } 53 54 func TestNew(t *testing.T) { 55 aggregatorSpecs := []aggregator.Spec{ 56 aggregator.MustNew("numMatches", "count", "true()"), 57 aggregator.MustNew( 58 "percentMatches", 59 "calc", 60 "roundto(100.0 * numMatches / numRecords, 2)", 61 ), 62 aggregator.MustNew("numIncomeGt2", "count", "income > 2"), 63 aggregator.MustNew("goalsScore", "goalsscore"), 64 } 65 goals := []*goal.Goal{ 66 goal.MustNew("numIncomeGt2 == 1"), 67 goal.MustNew("numIncomeGt2 == 2"), 68 } 69 70 assessment := rhkassessment.New(aggregatorSpecs, goals) 71 assessment.RuleAssessments = []*rhkassessment.RuleAssessment{ 72 &rhkassessment.RuleAssessment{ 73 Rule: rule.NewEQFV("month", dlit.NewString("may")), 74 Aggregators: map[string]*dlit.Literal{ 75 "numMatches": dlit.MustNew("2142"), 76 "percentMatches": dlit.MustNew("242"), 77 "numIncomeGt2": dlit.MustNew("22"), 78 "goalsScore": dlit.MustNew(20.1), 79 }, 80 Goals: []*rhkassessment.GoalAssessment{ 81 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 82 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", false}, 83 }, 84 }, 85 &rhkassessment.RuleAssessment{ 86 Rule: rule.NewGEFV("rate", dlit.MustNew(789.2)), 87 Aggregators: map[string]*dlit.Literal{ 88 "numMatches": dlit.MustNew("3142"), 89 "percentMatches": dlit.MustNew("342"), 90 "numIncomeGt2": dlit.MustNew("32"), 91 "goalsScore": dlit.MustNew(30.1), 92 }, 93 Goals: []*rhkassessment.GoalAssessment{ 94 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 95 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", false}, 96 }, 97 }, 98 &rhkassessment.RuleAssessment{ 99 Rule: rule.NewTrue(), 100 Aggregators: map[string]*dlit.Literal{ 101 "numMatches": dlit.MustNew("142"), 102 "percentMatches": dlit.MustNew("42"), 103 "numIncomeGt2": dlit.MustNew("2"), 104 "goalsScore": dlit.MustNew(0.1), 105 }, 106 Goals: []*rhkassessment.GoalAssessment{ 107 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 108 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 109 }, 110 }, 111 } 112 113 wantReport := &Report{ 114 Mode: Train, 115 Title: "some title", 116 Tags: []string{"bank", "test / fred"}, 117 Category: "testing", 118 Stamp: time.Now(), 119 ExperimentFilename: "somename.yaml", 120 NumRecords: assessment.NumRecords, 121 SortOrder: []rhkassessment.SortOrder{ 122 rhkassessment.SortOrder{ 123 Aggregator: "goalsScore", 124 Direction: rhkassessment.DESCENDING, 125 }, 126 rhkassessment.SortOrder{ 127 Aggregator: "percentMatches", 128 Direction: rhkassessment.ASCENDING, 129 }, 130 }, 131 Aggregators: []AggregatorDesc{ 132 AggregatorDesc{Name: "numMatches", Kind: "count", Arg: "true()"}, 133 AggregatorDesc{ 134 Name: "percentMatches", 135 Kind: "calc", 136 Arg: "roundto(100.0 * numMatches / numRecords, 2)", 137 }, 138 AggregatorDesc{Name: "numIncomeGt2", Kind: "count", Arg: "income > 2"}, 139 AggregatorDesc{Name: "goalsScore", Kind: "goalsscore", Arg: ""}, 140 }, 141 Description: testDescription, 142 Assessments: []*Assessment{ 143 &Assessment{ 144 Rule: "rate >= 789.2", 145 Aggregators: []*Aggregator{ 146 &Aggregator{ 147 Name: "goalsScore", 148 OriginalValue: "0.1", 149 RuleValue: "30.1", 150 Difference: "30", 151 }, 152 &Aggregator{ 153 Name: "numIncomeGt2", 154 OriginalValue: "2", 155 RuleValue: "32", 156 Difference: "30", 157 }, 158 &Aggregator{ 159 Name: "numMatches", 160 OriginalValue: "142", 161 RuleValue: "3142", 162 Difference: "3000", 163 }, 164 &Aggregator{ 165 Name: "percentMatches", 166 OriginalValue: "42", 167 RuleValue: "342", 168 Difference: "300", 169 }, 170 }, 171 Goals: []*Goal{ 172 &Goal{ 173 Expr: "numIncomeGt2 == 1", 174 OriginalPassed: false, 175 RulePassed: false, 176 }, 177 &Goal{ 178 Expr: "numIncomeGt2 == 2", 179 OriginalPassed: true, 180 RulePassed: false, 181 }, 182 }, 183 }, 184 &Assessment{ 185 Rule: "month == \"may\"", 186 Aggregators: []*Aggregator{ 187 &Aggregator{ 188 Name: "goalsScore", 189 OriginalValue: "0.1", 190 RuleValue: "20.1", 191 Difference: "20", 192 }, 193 &Aggregator{ 194 Name: "numIncomeGt2", 195 OriginalValue: "2", 196 RuleValue: "22", 197 Difference: "20", 198 }, 199 &Aggregator{ 200 Name: "numMatches", 201 OriginalValue: "142", 202 RuleValue: "2142", 203 Difference: "2000", 204 }, 205 &Aggregator{ 206 Name: "percentMatches", 207 OriginalValue: "42", 208 RuleValue: "242", 209 Difference: "200", 210 }, 211 }, 212 Goals: []*Goal{ 213 &Goal{ 214 Expr: "numIncomeGt2 == 1", 215 OriginalPassed: false, 216 RulePassed: false, 217 }, 218 &Goal{ 219 Expr: "numIncomeGt2 == 2", 220 OriginalPassed: true, 221 RulePassed: false, 222 }, 223 }, 224 }, 225 &Assessment{ 226 Rule: "true()", 227 Aggregators: []*Aggregator{ 228 &Aggregator{ 229 Name: "goalsScore", 230 OriginalValue: "0.1", 231 RuleValue: "0.1", 232 Difference: "0", 233 }, 234 &Aggregator{ 235 Name: "numIncomeGt2", 236 OriginalValue: "2", 237 RuleValue: "2", 238 Difference: "0", 239 }, 240 &Aggregator{ 241 Name: "numMatches", 242 OriginalValue: "142", 243 RuleValue: "142", 244 Difference: "0", 245 }, 246 &Aggregator{ 247 Name: "percentMatches", 248 OriginalValue: "42", 249 RuleValue: "42", 250 Difference: "0", 251 }, 252 }, 253 Goals: []*Goal{ 254 &Goal{ 255 Expr: "numIncomeGt2 == 1", 256 OriginalPassed: false, 257 RulePassed: false, 258 }, 259 &Goal{ 260 Expr: "numIncomeGt2 == 2", 261 OriginalPassed: true, 262 RulePassed: true, 263 }, 264 }, 265 }, 266 }, 267 } 268 got := New( 269 wantReport.Mode, 270 wantReport.Title, 271 wantReport.Description, 272 assessment, 273 aggregatorSpecs, 274 wantReport.SortOrder, 275 wantReport.ExperimentFilename, 276 wantReport.Tags, 277 wantReport.Category, 278 ) 279 if err := checkReportsMatch(got, wantReport); err != nil { 280 t.Errorf("New: %s", err) 281 } 282 } 283 284 func TestNew_single_true_rule(t *testing.T) { 285 aggregatorSpecs := []aggregator.Spec{ 286 aggregator.MustNew("numMatches", "count", "true()"), 287 aggregator.MustNew( 288 "percentMatches", 289 "calc", 290 "roundto(100.0 * numMatches / numRecords, 2)", 291 ), 292 aggregator.MustNew("numIncomeGt2", "count", "income > 2"), 293 aggregator.MustNew("goalsScore", "goalsscore"), 294 } 295 goals := []*goal.Goal{ 296 goal.MustNew("numIncomeGt2 == 1"), 297 goal.MustNew("numIncomeGt2 == 2"), 298 } 299 300 assessment := rhkassessment.New(aggregatorSpecs, goals) 301 assessment.RuleAssessments = []*rhkassessment.RuleAssessment{ 302 &rhkassessment.RuleAssessment{ 303 Rule: rule.NewTrue(), 304 Aggregators: map[string]*dlit.Literal{ 305 "numMatches": dlit.MustNew("142"), 306 "percentMatches": dlit.MustNew("42"), 307 "numIncomeGt2": dlit.MustNew("2"), 308 "goalsScore": dlit.MustNew(0.1), 309 }, 310 Goals: []*rhkassessment.GoalAssessment{ 311 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 312 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 313 }, 314 }, 315 } 316 317 wantReport := &Report{ 318 Mode: Train, 319 Title: "some title", 320 Tags: []string{"bank", "test / fred"}, 321 Category: "testing", 322 Stamp: time.Now(), 323 ExperimentFilename: "somename.yaml", 324 NumRecords: assessment.NumRecords, 325 SortOrder: []rhkassessment.SortOrder{ 326 rhkassessment.SortOrder{ 327 Aggregator: "goalsScore", 328 Direction: rhkassessment.DESCENDING, 329 }, 330 rhkassessment.SortOrder{ 331 Aggregator: "percentMatches", 332 Direction: rhkassessment.ASCENDING, 333 }, 334 }, 335 Aggregators: []AggregatorDesc{ 336 AggregatorDesc{Name: "numMatches", Kind: "count", Arg: "true()"}, 337 AggregatorDesc{ 338 Name: "percentMatches", 339 Kind: "calc", 340 Arg: "roundto(100.0 * numMatches / numRecords, 2)", 341 }, 342 AggregatorDesc{Name: "numIncomeGt2", Kind: "count", Arg: "income > 2"}, 343 AggregatorDesc{Name: "goalsScore", Kind: "goalsscore", Arg: ""}, 344 }, 345 Description: testDescription, 346 Assessments: []*Assessment{ 347 &Assessment{ 348 Rule: "true()", 349 Aggregators: []*Aggregator{ 350 &Aggregator{ 351 Name: "goalsScore", 352 OriginalValue: "0.1", 353 RuleValue: "0.1", 354 Difference: "0", 355 }, 356 &Aggregator{ 357 Name: "numIncomeGt2", 358 OriginalValue: "2", 359 RuleValue: "2", 360 Difference: "0", 361 }, 362 &Aggregator{ 363 Name: "numMatches", 364 OriginalValue: "142", 365 RuleValue: "142", 366 Difference: "0", 367 }, 368 &Aggregator{ 369 Name: "percentMatches", 370 OriginalValue: "42", 371 RuleValue: "42", 372 Difference: "0", 373 }, 374 }, 375 Goals: []*Goal{ 376 &Goal{ 377 Expr: "numIncomeGt2 == 1", 378 OriginalPassed: false, 379 RulePassed: false, 380 }, 381 &Goal{ 382 Expr: "numIncomeGt2 == 2", 383 OriginalPassed: true, 384 RulePassed: true, 385 }, 386 }, 387 }, 388 }, 389 } 390 got := New( 391 wantReport.Mode, 392 wantReport.Title, 393 wantReport.Description, 394 assessment, 395 aggregatorSpecs, 396 wantReport.SortOrder, 397 wantReport.ExperimentFilename, 398 wantReport.Tags, 399 wantReport.Category, 400 ) 401 if err := checkReportsMatch(got, wantReport); err != nil { 402 t.Errorf("New: %s", err) 403 } 404 } 405 func TestLoadJSON_errors(t *testing.T) { 406 // File mode permission used as standard: 407 // No special permission bits 408 // User: Read, Write Execute 409 // Group: Read 410 // Other: None 411 const modePerm = 0740 412 tmpDir := testhelpers.TempDir(t) 413 defer os.RemoveAll(tmpDir) 414 cfg := &config.Config{BuildDir: tmpDir} 415 reportsDir := filepath.Join(tmpDir, "reports") 416 if err := os.MkdirAll(reportsDir, modePerm); err != nil { 417 t.Fatalf("MkdirAll: %s", err) 418 } 419 testhelpers.CopyFile(t, filepath.Join("fixtures", "empty.json"), reportsDir) 420 421 cases := []struct { 422 filename string 423 wantErr error 424 }{ 425 {filename: "nonexistent.json", 426 wantErr: &os.PathError{ 427 Op: "open", 428 Path: filepath.Join(reportsDir, "nonexistent.json"), 429 Err: syscall.ENOENT, 430 }, 431 }, 432 {filename: "empty.json", 433 wantErr: fmt.Errorf( 434 "can't decode JSON file: %s, %s", 435 filepath.Join(reportsDir, "empty.json"), 436 io.EOF), 437 }, 438 } 439 for _, c := range cases { 440 got, err := LoadJSON(cfg, c.filename) 441 if got != nil { 442 t.Errorf("LoadJSON: got: %v, want: nil", got) 443 } 444 if err == nil || err.Error() != c.wantErr.Error() { 445 t.Errorf("LoadJSON: gotErr: %s, wantErr: %s", err, c.wantErr) 446 } 447 } 448 } 449 450 func TestWriteLoadJSON(t *testing.T) { 451 // File mode permission used as standard: 452 // No special permission bits 453 // User: Read, Write Execute 454 // Group: Read 455 // Other: None 456 const modePerm = 0740 457 458 tmpDir := testhelpers.TempDir(t) 459 defer os.RemoveAll(tmpDir) 460 reportsDir := filepath.Join(tmpDir, "reports") 461 if err := os.MkdirAll(reportsDir, modePerm); err != nil { 462 t.Fatalf("MkdirAll: %s", err) 463 } 464 goals := []*goal.Goal{ 465 goal.MustNew("numIncomeGt2 == 1"), 466 goal.MustNew("numIncomeGt2 == 2"), 467 } 468 aggregators := []aggregator.Spec{ 469 aggregator.MustNew("numMatches", "count", "true()"), 470 aggregator.MustNew( 471 "percentMatches", 472 "calc", 473 "roundto(100.0 * numMatches / numRecords, 2)", 474 ), 475 aggregator.MustNew("numIncomeGt2", "count", "income > 2"), 476 aggregator.MustNew("goalsScore", "goalsscore"), 477 } 478 assessment := rhkassessment.New(aggregators, goals) 479 assessment.RuleAssessments = []*rhkassessment.RuleAssessment{ 480 &rhkassessment.RuleAssessment{ 481 Rule: rule.NewEQFV("month", dlit.NewString("may")), 482 Aggregators: map[string]*dlit.Literal{ 483 "numMatches": dlit.MustNew("2142"), 484 "percentMatches": dlit.MustNew("242"), 485 "numIncomeGt2": dlit.MustNew("22"), 486 "goalsScore": dlit.MustNew(20.1), 487 }, 488 Goals: []*rhkassessment.GoalAssessment{ 489 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 490 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 491 }, 492 }, 493 &rhkassessment.RuleAssessment{ 494 Rule: rule.NewGEFV("rate", dlit.MustNew(789.2)), 495 Aggregators: map[string]*dlit.Literal{ 496 "numMatches": dlit.MustNew("3142"), 497 "percentMatches": dlit.MustNew("342"), 498 "numIncomeGt2": dlit.MustNew("32"), 499 "goalsScore": dlit.MustNew(30.1), 500 }, 501 Goals: []*rhkassessment.GoalAssessment{ 502 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 503 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 504 }, 505 }, 506 &rhkassessment.RuleAssessment{ 507 Rule: rule.NewTrue(), 508 Aggregators: map[string]*dlit.Literal{ 509 "numMatches": dlit.MustNew("142"), 510 "percentMatches": dlit.MustNew("42"), 511 "numIncomeGt2": dlit.MustNew("2"), 512 "goalsScore": dlit.MustNew(0.1), 513 }, 514 Goals: []*rhkassessment.GoalAssessment{ 515 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 516 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 517 }, 518 }, 519 } 520 521 title := "some title" 522 sortOrder := []rhkassessment.SortOrder{ 523 rhkassessment.SortOrder{ 524 Aggregator: "goalsScore", 525 Direction: rhkassessment.DESCENDING, 526 }, 527 rhkassessment.SortOrder{ 528 Aggregator: "percentMatches", 529 Direction: rhkassessment.ASCENDING, 530 }, 531 } 532 experimentFilename := "somename.yaml" 533 tags := []string{"bank", "test / fred"} 534 category := "testing" 535 config := &config.Config{BuildDir: tmpDir} 536 report := New( 537 Train, 538 title, 539 testDescription, 540 assessment, 541 aggregators, 542 sortOrder, 543 experimentFilename, 544 tags, 545 category, 546 ) 547 548 if err := report.WriteJSON(config); err != nil { 549 t.Fatalf("WriteJSON: %s", err) 550 } 551 buildFilename := internal.MakeBuildFilename(Train.String(), category, title) 552 loadedReport, err := LoadJSON(config, buildFilename) 553 if err != nil { 554 t.Fatalf("LoadJSON: %s", err) 555 } 556 if err := checkReportsMatch(report, loadedReport); err != nil { 557 t.Errorf("Reports don't match: %s", err) 558 } 559 } 560 561 func TestCalcTrueAggregatorDiff(t *testing.T) { 562 trueAggregators := map[string]*dlit.Literal{ 563 "numMatches": dlit.MustNew(176), 564 "profit": dlit.MustNew(23), 565 "bigNum": dlit.MustNew(int64(math.MaxInt64)), 566 "flow": dlit.NewString("11.22"), 567 } 568 cases := []struct { 569 name string 570 value *dlit.Literal 571 want string 572 }{ 573 {name: "numMatches", value: dlit.MustNew(192), want: "16"}, 574 {name: "numMatches", value: dlit.MustNew(165), want: "-11"}, 575 {name: "flow", value: dlit.NewString("16.45"), want: "5.23"}, 576 {name: "bigNum", 577 value: dlit.MustNew(int64(math.MinInt64)), 578 want: dlit.MustNew( 579 float64(math.MinInt64) - float64(math.MaxInt64), 580 ).String(), 581 }, 582 {name: "bigNum", 583 value: dlit.MustNew(errors.New("some error")), 584 want: "N/A", 585 }, 586 } 587 588 for _, c := range cases { 589 got := calcTrueAggregatorDiff(trueAggregators, c.name, c.value) 590 if got != c.want { 591 t.Errorf("calcTrueAggregatorDifference(trueAggregators, %v, %v) got: %s, want: %s", 592 c.name, c.value, got, c.want) 593 } 594 } 595 } 596 597 func TestLoadJSON_multiple_attempts(t *testing.T) { 598 // File mode permission used as standard: 599 // No special permission bits 600 // User: Read, Write Execute 601 // Group: Read 602 // Other: None 603 const modePerm = 0740 604 605 tmpDir := testhelpers.TempDir(t) 606 defer os.RemoveAll(tmpDir) 607 reportsDir := filepath.Join(tmpDir, "reports") 608 if err := os.MkdirAll(reportsDir, modePerm); err != nil { 609 t.Fatalf("MkdirAll: %s", err) 610 } 611 aggregators := []aggregator.Spec{ 612 aggregator.MustNew("numMatches", "count", "true()"), 613 aggregator.MustNew( 614 "percentMatches", 615 "calc", 616 "roundto(100.0 * numMatches / numRecords, 2)", 617 ), 618 aggregator.MustNew("numIncomeGt2", "count", "income > 2"), 619 aggregator.MustNew("goalsScore", "goalsscore"), 620 } 621 goals := []*goal.Goal{ 622 goal.MustNew("numIncomeGt2 == 1"), 623 goal.MustNew("numIncomeGt2 == 2"), 624 } 625 assessment := rhkassessment.New(aggregators, goals) 626 assessment.RuleAssessments = []*rhkassessment.RuleAssessment{ 627 &rhkassessment.RuleAssessment{ 628 Rule: rule.NewEQFV("month", dlit.NewString("may")), 629 Aggregators: map[string]*dlit.Literal{ 630 "numMatches": dlit.MustNew("2142"), 631 "percentMatches": dlit.MustNew("242"), 632 "numIncomeGt2": dlit.MustNew("22"), 633 "goalsScore": dlit.MustNew(20.1), 634 }, 635 Goals: []*rhkassessment.GoalAssessment{ 636 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 637 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 638 }, 639 }, 640 &rhkassessment.RuleAssessment{ 641 Rule: rule.NewGEFV("rate", dlit.MustNew(789.2)), 642 Aggregators: map[string]*dlit.Literal{ 643 "numMatches": dlit.MustNew("3142"), 644 "percentMatches": dlit.MustNew("342"), 645 "numIncomeGt2": dlit.MustNew("32"), 646 "goalsScore": dlit.MustNew(30.1), 647 }, 648 Goals: []*rhkassessment.GoalAssessment{ 649 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 650 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 651 }, 652 }, 653 &rhkassessment.RuleAssessment{ 654 Rule: rule.NewTrue(), 655 Aggregators: map[string]*dlit.Literal{ 656 "numMatches": dlit.MustNew("142"), 657 "percentMatches": dlit.MustNew("42"), 658 "numIncomeGt2": dlit.MustNew("2"), 659 "goalsScore": dlit.MustNew(0.1), 660 }, 661 Goals: []*rhkassessment.GoalAssessment{ 662 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 663 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 664 }, 665 }, 666 } 667 668 title := "some title" 669 sortOrder := []rhkassessment.SortOrder{ 670 rhkassessment.SortOrder{ 671 Aggregator: "goalsScore", 672 Direction: rhkassessment.DESCENDING, 673 }, 674 rhkassessment.SortOrder{ 675 Aggregator: "percentMatches", 676 Direction: rhkassessment.ASCENDING, 677 }, 678 } 679 experimentFilename := "somename.yaml" 680 tags := []string{"bank", "test / fred"} 681 category := "testing" 682 config := &config.Config{BuildDir: tmpDir} 683 report := New( 684 Train, 685 title, 686 testDescription, 687 assessment, 688 aggregators, 689 sortOrder, 690 experimentFilename, 691 tags, 692 category, 693 ) 694 695 buildFilename := internal.MakeBuildFilename(Train.String(), category, title) 696 testhelpers.CopyFile( 697 t, 698 filepath.Join("fixtures", "empty.json"), 699 reportsDir, 700 buildFilename, 701 ) 702 go func() { 703 time.Sleep(500 * time.Millisecond) 704 if err := report.WriteJSON(config); err != nil { 705 t.Fatalf("WriteJSON: %s", err) 706 } 707 }() 708 maxLoadAttempts := 5 709 loadedReport, err := LoadJSON(config, buildFilename, maxLoadAttempts) 710 if err != nil { 711 t.Fatalf("LoadJSON: %s", err) 712 } 713 if err := checkReportsMatch(report, loadedReport); err != nil { 714 t.Errorf("Reports don't match: %s", err) 715 } 716 } 717 718 func TestGetSortedAggregatorNames(t *testing.T) { 719 aggregators := map[string]*dlit.Literal{ 720 "numMatches": dlit.MustNew(176), 721 "profit": dlit.MustNew(23), 722 "bigNum": dlit.MustNew(int64(math.MaxInt64)), 723 } 724 want := []string{"bigNum", "numMatches", "profit"} 725 got := getSortedAggregatorNames(aggregators) 726 if !reflect.DeepEqual(got, want) { 727 t.Errorf("getSortedAggregatorNames - got: %v, want: %v", got, want) 728 } 729 } 730 731 func TestGetTrueRuleAssessment(t *testing.T) { 732 assessment := &rhkassessment.Assessment{ 733 NumRecords: 20, 734 RuleAssessments: []*rhkassessment.RuleAssessment{ 735 &rhkassessment.RuleAssessment{ 736 Rule: rule.NewEQFV("month", dlit.NewString("may")), 737 Aggregators: map[string]*dlit.Literal{ 738 "numMatches": dlit.MustNew("2142"), 739 "percentMatches": dlit.MustNew("242"), 740 "numIncomeGt2": dlit.MustNew("22"), 741 "goalsScore": dlit.MustNew(20.1), 742 }, 743 Goals: []*rhkassessment.GoalAssessment{ 744 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 745 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 746 }, 747 }, 748 &rhkassessment.RuleAssessment{ 749 Rule: rule.NewGEFV("rate", dlit.MustNew(789.2)), 750 Aggregators: map[string]*dlit.Literal{ 751 "numMatches": dlit.MustNew("3142"), 752 "percentMatches": dlit.MustNew("342"), 753 "numIncomeGt2": dlit.MustNew("32"), 754 "goalsScore": dlit.MustNew(30.1), 755 }, 756 Goals: []*rhkassessment.GoalAssessment{ 757 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 758 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 759 }, 760 }, 761 &rhkassessment.RuleAssessment{ 762 Rule: rule.NewTrue(), 763 Aggregators: map[string]*dlit.Literal{ 764 "numMatches": dlit.MustNew("142"), 765 "percentMatches": dlit.MustNew("42"), 766 "numIncomeGt2": dlit.MustNew("2"), 767 "goalsScore": dlit.MustNew(0.1), 768 }, 769 Goals: []*rhkassessment.GoalAssessment{ 770 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 771 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 772 }, 773 }, 774 }, 775 } 776 want := &rhkassessment.RuleAssessment{ 777 Rule: rule.NewTrue(), 778 Aggregators: map[string]*dlit.Literal{ 779 "numMatches": dlit.MustNew("142"), 780 "percentMatches": dlit.MustNew("42"), 781 "numIncomeGt2": dlit.MustNew("2"), 782 "goalsScore": dlit.MustNew(0.1), 783 }, 784 Goals: []*rhkassessment.GoalAssessment{ 785 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 786 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 787 }, 788 } 789 790 got, err := getTrueRuleAssessment(assessment) 791 if err != nil { 792 t.Fatalf("getTrueRuleAssessment: %s", err) 793 } 794 if !reflect.DeepEqual(got, want) { 795 t.Errorf("getTrueAggregators - got: %v, want: %v", got, want) 796 } 797 } 798 799 func TestGetTrueRuleAssessment_error(t *testing.T) { 800 assessment := &rhkassessment.Assessment{ 801 NumRecords: 20, 802 RuleAssessments: []*rhkassessment.RuleAssessment{ 803 &rhkassessment.RuleAssessment{ 804 Rule: rule.NewEQFV("month", dlit.NewString("may")), 805 Aggregators: map[string]*dlit.Literal{ 806 "numMatches": dlit.MustNew("2142"), 807 "percentMatches": dlit.MustNew("242"), 808 "numIncomeGt2": dlit.MustNew("22"), 809 "goalsScore": dlit.MustNew(20.1), 810 }, 811 Goals: []*rhkassessment.GoalAssessment{ 812 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 813 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 814 }, 815 }, 816 &rhkassessment.RuleAssessment{ 817 Rule: rule.NewGEFV("rate", dlit.MustNew(789.2)), 818 Aggregators: map[string]*dlit.Literal{ 819 "numMatches": dlit.MustNew("3142"), 820 "percentMatches": dlit.MustNew("342"), 821 "numIncomeGt2": dlit.MustNew("32"), 822 "goalsScore": dlit.MustNew(30.1), 823 }, 824 Goals: []*rhkassessment.GoalAssessment{ 825 &rhkassessment.GoalAssessment{"numIncomeGt2 == 1", false}, 826 &rhkassessment.GoalAssessment{"numIncomeGt2 == 2", true}, 827 }, 828 }, 829 }, 830 } 831 wantErr := errors.New("can't find true() rule") 832 833 _, err := getTrueRuleAssessment(assessment) 834 if err == nil || err.Error() != wantErr.Error() { 835 t.Errorf("getTrueAggregators: err: %s, wantErr: %s", err, wantErr) 836 } 837 } 838 839 /****************************** 840 * Helper Functions 841 ******************************/ 842 843 func checkReportsMatch(r1, r2 *Report) error { 844 if r1.Mode != r2.Mode { 845 return fmt.Errorf("Modes don't match - %s != %s", r1.Mode, r2.Mode) 846 } 847 if r1.Title != r2.Title { 848 return fmt.Errorf("Titles don't match - %s != %s", r1.Title, r2.Title) 849 } 850 if !reflect.DeepEqual(r1.Tags, r2.Tags) { 851 return fmt.Errorf("Tags don't match - %v != %v", r1.Tags, r2.Tags) 852 } 853 if r1.Category != r2.Category { 854 return fmt.Errorf("Categories don't match - %s != %s", 855 r1.Category, r2.Category) 856 } 857 if math.Abs(r1.Stamp.Sub(r2.Stamp).Seconds()) > 1 { 858 return fmt.Errorf("Stamps don't match - %s != %s", r1.Stamp, r2.Stamp) 859 } 860 if r1.ExperimentFilename != r2.ExperimentFilename { 861 return fmt.Errorf("ExperimentFilenames don't match - %s != %s", 862 r1.ExperimentFilename, r2.ExperimentFilename) 863 } 864 if r1.NumRecords != r2.NumRecords { 865 return fmt.Errorf("NumRecords don't match - %d != %d", 866 r1.NumRecords, r2.NumRecords) 867 } 868 if !reflect.DeepEqual(r1.SortOrder, r2.SortOrder) { 869 return fmt.Errorf("SortOrder don't match - %v != %v", 870 r1.SortOrder, r2.SortOrder) 871 } 872 if !reflect.DeepEqual(r1.Aggregators, r2.Aggregators) { 873 return fmt.Errorf("Aggregators don't match - %v != %v", 874 r1.Aggregators, r2.Aggregators) 875 } 876 if err := checkAssessmentsMatch(r1.Assessments, r2.Assessments); err != nil { 877 return fmt.Errorf("Assessments don't match: %s - %v != %v", 878 err, r1.Assessments, r2.Assessments) 879 } 880 return nil 881 } 882 883 func checkAssessmentsMatch(as1, as2 []*Assessment) error { 884 if len(as1) != len(as2) { 885 return fmt.Errorf("number of Assessments don't match %d != %d", 886 len(as1), len(as2)) 887 } 888 for i, assessment1 := range as1 { 889 if assessment1.Rule != as2[i].Rule { 890 return fmt.Errorf("assessment[%d] Rules don't match: %v != %v", 891 i, assessment1.Rule, as2[i].Rule) 892 } 893 if !reflect.DeepEqual(assessment1.Aggregators, as2[i].Aggregators) { 894 return fmt.Errorf("assessment[%d] Aggregators don't match: %v != %v", 895 i, assessment1.Aggregators, as2[i].Aggregators) 896 } 897 if !reflect.DeepEqual(assessment1.Goals, as2[i].Goals) { 898 return fmt.Errorf("assessment[%d] Goals don't match: %v != %v", 899 i, assessment1.Goals, as2[i].Goals) 900 } 901 } 902 return nil 903 }