github.com/vlifesystems/rulehunter@v0.0.0-20180501090014-673078aa4a83/html/report_test.go (about)

     1  package html
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/lawrencewoodman/dlit"
    12  	"github.com/vlifesystems/rhkit/assessment"
    13  	"github.com/vlifesystems/rhkit/description"
    14  	"github.com/vlifesystems/rulehunter/config"
    15  	"github.com/vlifesystems/rulehunter/internal/testhelpers"
    16  	"github.com/vlifesystems/rulehunter/report"
    17  )
    18  
    19  var testDescription = &description.Description{
    20  	map[string]*description.Field{
    21  		"month": {description.String, nil, nil, 0,
    22  			map[string]description.Value{
    23  				"feb":  {dlit.MustNew("feb"), 3},
    24  				"may":  {dlit.MustNew("may"), 2},
    25  				"june": {dlit.MustNew("june"), 9},
    26  			},
    27  			3,
    28  		},
    29  		"rate": {
    30  			description.Number,
    31  			dlit.MustNew(0.3),
    32  			dlit.MustNew(15.1),
    33  			3,
    34  			map[string]description.Value{
    35  				"0.3":   {dlit.MustNew(0.3), 7},
    36  				"7":     {dlit.MustNew(7), 2},
    37  				"7.3":   {dlit.MustNew(7.3), 9},
    38  				"9.278": {dlit.MustNew(9.278), 4},
    39  			},
    40  			4,
    41  		},
    42  		"method": {description.Ignore, nil, nil, 0,
    43  			map[string]description.Value{}, -1},
    44  	},
    45  }
    46  
    47  func TestGenerateReport_single_true_rule(t *testing.T) {
    48  	report := &report.Report{
    49  		Mode:               report.Train,
    50  		Title:              "some title",
    51  		Tags:               []string{"bank", "test / fred"},
    52  		Category:           "testing",
    53  		Stamp:              time.Now(),
    54  		ExperimentFilename: "somename.yaml",
    55  		NumRecords:         1000,
    56  		SortOrder: []assessment.SortOrder{
    57  			assessment.SortOrder{
    58  				Aggregator: "goalsScore",
    59  				Direction:  assessment.DESCENDING,
    60  			},
    61  			assessment.SortOrder{
    62  				Aggregator: "percentMatches",
    63  				Direction:  assessment.ASCENDING,
    64  			},
    65  		},
    66  		Aggregators: []report.AggregatorDesc{
    67  			report.AggregatorDesc{Name: "numMatches", Kind: "count", Arg: "true()"},
    68  			report.AggregatorDesc{
    69  				Name: "percentMatches",
    70  				Kind: "calc",
    71  				Arg:  "roundto(100.0 * numMatches / numRecords, 2)",
    72  			},
    73  			report.AggregatorDesc{Name: "numIncomeGt2", Kind: "count", Arg: "income > 2"},
    74  			report.AggregatorDesc{Name: "goalsScore", Kind: "goalsscore", Arg: ""},
    75  		},
    76  		Description: testDescription,
    77  		Assessments: []*report.Assessment{
    78  			&report.Assessment{
    79  				Rule: "true()",
    80  				Aggregators: []*report.Aggregator{
    81  					&report.Aggregator{
    82  						Name:          "goalsScore",
    83  						OriginalValue: "0.1",
    84  						RuleValue:     "0.1",
    85  						Difference:    "0",
    86  					},
    87  					&report.Aggregator{
    88  						Name:          "numIncomeGt2",
    89  						OriginalValue: "2",
    90  						RuleValue:     "2",
    91  						Difference:    "0",
    92  					},
    93  					&report.Aggregator{
    94  						Name:          "numMatches",
    95  						OriginalValue: "142",
    96  						RuleValue:     "142",
    97  						Difference:    "0",
    98  					},
    99  					&report.Aggregator{
   100  						Name:          "percentMatches",
   101  						OriginalValue: "42",
   102  						RuleValue:     "42",
   103  						Difference:    "0",
   104  					},
   105  				},
   106  				Goals: []*report.Goal{
   107  					&report.Goal{
   108  						Expr:           "numIncomeGt2 == 1",
   109  						OriginalPassed: false,
   110  						RulePassed:     false,
   111  					},
   112  					&report.Goal{
   113  						Expr:           "numIncomeGt2 == 2",
   114  						OriginalPassed: true,
   115  						RulePassed:     true,
   116  					},
   117  				},
   118  			},
   119  		},
   120  	}
   121  
   122  	cfgDir := testhelpers.BuildConfigDirs(t, true)
   123  	defer os.RemoveAll(cfgDir)
   124  	cfg := &config.Config{
   125  		ExperimentsDir:  filepath.Join(cfgDir, "experiments"),
   126  		WWWDir:          filepath.Join(cfgDir, "www"),
   127  		BuildDir:        filepath.Join(cfgDir, "build"),
   128  		MaxNumRecords:   100,
   129  		MaxNumProcesses: 4,
   130  	}
   131  	wantReportURLDir := "reports/category/testing/some-title/train/"
   132  
   133  	reportURLDir, err := generateReport(report, cfg)
   134  	if err != nil {
   135  		t.Fatalf("generateReport: %s", err)
   136  	}
   137  	if reportURLDir != wantReportURLDir {
   138  		t.Errorf("generateReport - wantReportURLDir: %s, got: %s",
   139  			wantReportURLDir, reportURLDir)
   140  	}
   141  
   142  	htmlFilename := filepath.Join(
   143  		cfg.WWWDir,
   144  		"reports",
   145  		"category",
   146  		"testing",
   147  		"some-title",
   148  		"train",
   149  		"index.html",
   150  	)
   151  	// read the whole file at once
   152  	b, err := ioutil.ReadFile(htmlFilename)
   153  	if err != nil {
   154  		t.Fatalf("ReadFile: %s", err)
   155  	}
   156  	s := string(b)
   157  	wantText := "No rule found that improves on the original dataset"
   158  	dontWantText := "Original Value"
   159  	if !strings.Contains(s, wantText) {
   160  		t.Errorf("html file: %s, doesn't contain text \"%s\"",
   161  			htmlFilename, wantText)
   162  	}
   163  	if strings.Contains(s, dontWantText) {
   164  		t.Errorf("html file: %s, contains text \"%s\"",
   165  			htmlFilename, dontWantText)
   166  	}
   167  }
   168  
   169  func TestGenerateReport_two_rules(t *testing.T) {
   170  	report := &report.Report{
   171  		Mode:               report.Train,
   172  		Title:              "some title",
   173  		Tags:               []string{"bank", "test / fred"},
   174  		Category:           "testing",
   175  		Stamp:              time.Now(),
   176  		ExperimentFilename: "somename.yaml",
   177  		NumRecords:         1000,
   178  		SortOrder: []assessment.SortOrder{
   179  			assessment.SortOrder{
   180  				Aggregator: "goalsScore",
   181  				Direction:  assessment.DESCENDING,
   182  			},
   183  			assessment.SortOrder{
   184  				Aggregator: "percentMatches",
   185  				Direction:  assessment.ASCENDING,
   186  			},
   187  		},
   188  		Aggregators: []report.AggregatorDesc{
   189  			report.AggregatorDesc{Name: "numMatches", Kind: "count", Arg: "true()"},
   190  			report.AggregatorDesc{
   191  				Name: "percentMatches",
   192  				Kind: "calc",
   193  				Arg:  "roundto(100.0 * numMatches / numRecords, 2)",
   194  			},
   195  			report.AggregatorDesc{Name: "numIncomeGt2", Kind: "count", Arg: "income > 2"},
   196  			report.AggregatorDesc{Name: "goalsScore", Kind: "goalsscore", Arg: ""},
   197  		},
   198  		Description: testDescription,
   199  		Assessments: []*report.Assessment{
   200  			&report.Assessment{
   201  				Rule: "rate >= 789.2",
   202  				Aggregators: []*report.Aggregator{
   203  					&report.Aggregator{
   204  						Name:          "goalsScore",
   205  						OriginalValue: "0.1",
   206  						RuleValue:     "30.1",
   207  						Difference:    "30",
   208  					},
   209  					&report.Aggregator{
   210  						Name:          "numIncomeGt2",
   211  						OriginalValue: "2",
   212  						RuleValue:     "32",
   213  						Difference:    "30",
   214  					},
   215  					&report.Aggregator{
   216  						Name:          "numMatches",
   217  						OriginalValue: "142",
   218  						RuleValue:     "3142",
   219  						Difference:    "3000",
   220  					},
   221  					&report.Aggregator{
   222  						Name:          "percentMatches",
   223  						OriginalValue: "42",
   224  						RuleValue:     "342",
   225  						Difference:    "300",
   226  					},
   227  				},
   228  				Goals: []*report.Goal{
   229  					&report.Goal{
   230  						Expr:           "numIncomeGt2 == 1",
   231  						OriginalPassed: false,
   232  						RulePassed:     false,
   233  					},
   234  					&report.Goal{
   235  						Expr:           "numIncomeGt2 == 2",
   236  						OriginalPassed: true,
   237  						RulePassed:     false,
   238  					},
   239  				},
   240  			},
   241  			&report.Assessment{
   242  				Rule: "true()",
   243  				Aggregators: []*report.Aggregator{
   244  					&report.Aggregator{
   245  						Name:          "goalsScore",
   246  						OriginalValue: "0.1",
   247  						RuleValue:     "0.1",
   248  						Difference:    "0",
   249  					},
   250  					&report.Aggregator{
   251  						Name:          "numIncomeGt2",
   252  						OriginalValue: "2",
   253  						RuleValue:     "2",
   254  						Difference:    "0",
   255  					},
   256  					&report.Aggregator{
   257  						Name:          "numMatches",
   258  						OriginalValue: "142",
   259  						RuleValue:     "142",
   260  						Difference:    "0",
   261  					},
   262  					&report.Aggregator{
   263  						Name:          "percentMatches",
   264  						OriginalValue: "42",
   265  						RuleValue:     "42",
   266  						Difference:    "0",
   267  					},
   268  				},
   269  				Goals: []*report.Goal{
   270  					&report.Goal{
   271  						Expr:           "numIncomeGt2 == 1",
   272  						OriginalPassed: false,
   273  						RulePassed:     false,
   274  					},
   275  					&report.Goal{
   276  						Expr:           "numIncomeGt2 == 2",
   277  						OriginalPassed: true,
   278  						RulePassed:     true,
   279  					},
   280  				},
   281  			},
   282  		},
   283  	}
   284  
   285  	cfgDir := testhelpers.BuildConfigDirs(t, true)
   286  	defer os.RemoveAll(cfgDir)
   287  	cfg := &config.Config{
   288  		ExperimentsDir:  filepath.Join(cfgDir, "experiments"),
   289  		WWWDir:          filepath.Join(cfgDir, "www"),
   290  		BuildDir:        filepath.Join(cfgDir, "build"),
   291  		MaxNumRecords:   100,
   292  		MaxNumProcesses: 4,
   293  	}
   294  	wantReportURLDir := "reports/category/testing/some-title/train/"
   295  
   296  	reportURLDir, err := generateReport(report, cfg)
   297  	if err != nil {
   298  		t.Fatalf("generateReport: %s", err)
   299  	}
   300  	if reportURLDir != wantReportURLDir {
   301  		t.Errorf("generateReport - wantReportURLDir: %s, got: %s",
   302  			wantReportURLDir, reportURLDir)
   303  	}
   304  
   305  	htmlFilename := filepath.Join(
   306  		cfg.WWWDir,
   307  		"reports",
   308  		"category",
   309  		"testing",
   310  		"some-title",
   311  		"train",
   312  		"index.html",
   313  	)
   314  	// read the whole file at once
   315  	b, err := ioutil.ReadFile(htmlFilename)
   316  	if err != nil {
   317  		t.Fatalf("ReadFile: %s", err)
   318  	}
   319  	s := string(b)
   320  
   321  	wantText := "Original Value"
   322  	dontWantText := "No rule found that improves on the original dataset"
   323  	if !strings.Contains(s, wantText) {
   324  		t.Errorf("html file: %s, doesn't contain text \"%s\"",
   325  			htmlFilename, wantText)
   326  	}
   327  	if strings.Contains(s, dontWantText) {
   328  		t.Errorf("html file: %s, contains text \"%s\"",
   329  			htmlFilename, dontWantText)
   330  	}
   331  }
   332  
   333  func TestGenReportFilename(t *testing.T) {
   334  	cases := []struct {
   335  		stamp        time.Time
   336  		mode         report.ModeKind
   337  		category     string
   338  		title        string
   339  		wantFilename string
   340  	}{
   341  		{stamp: time.Date(2009, time.November, 10, 22, 19, 18, 17, time.UTC),
   342  			mode:     report.Train,
   343  			category: "",
   344  			title:    "This could be very interesting",
   345  			wantFilename: filepath.Join(
   346  				"reports",
   347  				"nocategory",
   348  				"this-could-be-very-interesting",
   349  				"train",
   350  				"index.html",
   351  			)},
   352  		{stamp: time.Date(2009, time.November, 10, 22, 19, 18, 17, time.UTC),
   353  			mode:     report.Train,
   354  			category: "acme or emca",
   355  			title:    "This could be very interesting",
   356  			wantFilename: filepath.Join(
   357  				"reports",
   358  				"category",
   359  				"acme-or-emca",
   360  				"this-could-be-very-interesting",
   361  				"train",
   362  				"index.html",
   363  			)},
   364  		{stamp: time.Date(2009, time.November, 10, 22, 19, 18, 17, time.UTC),
   365  			mode:     report.Test,
   366  			category: "",
   367  			title:    "This could be very interesting",
   368  			wantFilename: filepath.Join(
   369  				"reports",
   370  				"nocategory",
   371  				"this-could-be-very-interesting",
   372  				"test",
   373  				"index.html",
   374  			)},
   375  		{stamp: time.Date(2009, time.November, 10, 22, 19, 18, 17, time.UTC),
   376  			mode:     report.Test,
   377  			category: "acme or emca",
   378  			title:    "This could be very interesting",
   379  			wantFilename: filepath.Join(
   380  				"reports",
   381  				"category",
   382  				"acme-or-emca",
   383  				"this-could-be-very-interesting",
   384  				"test",
   385  				"index.html",
   386  			)},
   387  	}
   388  	for _, c := range cases {
   389  		got := genReportFilename(c.mode, c.category, c.title)
   390  		if got != c.wantFilename {
   391  			t.Errorf("genReportFilename(%s, %s) got: %s, want: %s",
   392  				c.stamp, c.title, got, c.wantFilename)
   393  		}
   394  	}
   395  }