github.com/tilt-dev/wat@v0.0.2-0.20180626175338-9349b638e250/cli/wat/decide_test.go (about)

     1  package wat
     2  
     3  import (
     4  	"math"
     5  	"reflect"
     6  	"testing"
     7  	"time"
     8  )
     9  
    10  var fileA = fileInfo{name: "a.txt", modTime: time.Now()}
    11  var fileB = fileInfo{name: "b.txt", modTime: time.Now().Add(-time.Minute)}
    12  var fileC = fileInfo{name: "c.txt", modTime: time.Now().Add(-time.Minute * 2)}
    13  var fileD = fileInfo{name: "d.txt", modTime: time.Now().Add(-time.Minute * 3)}
    14  
    15  var cmdA = WatCommand{Command: "cat a.txt", FilePattern: "a.txt"}
    16  var cmdB = WatCommand{Command: "cat b.txt", FilePattern: "b.txt"}
    17  var cmdC = WatCommand{Command: "cat c.txt", FilePattern: "c.txt"}
    18  var cmdD = WatCommand{Command: "cat d.txt", FilePattern: "d.txt"}
    19  
    20  var cmdLogA = newTestLog(cmdA, time.Minute, true)
    21  var cmdLogB = newTestLog(cmdB, 30*time.Second, true)
    22  var cmdLogC = newTestLog(cmdC, 20*time.Second, true)
    23  var cmdLogD = newTestLog(cmdD, 10*time.Second, true)
    24  
    25  var cmdLogSecSuccessA = newTestLog(cmdA, time.Second, true)
    26  var cmdLogSecSuccessB = newTestLog(cmdB, time.Second, true)
    27  var cmdLogSecSuccessC = newTestLog(cmdC, time.Second, true)
    28  var cmdLogSecSuccessD = newTestLog(cmdD, time.Second, true)
    29  var cmdLogSecFailA = newTestLog(cmdA, time.Second, false)
    30  var cmdLogSecFailB = newTestLog(cmdB, time.Second, false)
    31  var cmdLogSecFailC = newTestLog(cmdC, time.Second, false)
    32  var cmdLogSecFailD = newTestLog(cmdD, time.Second, false)
    33  var cmdLogMinuteFailA = newTestLog(cmdA, time.Minute, false)
    34  
    35  func TestNoCommands(t *testing.T) {
    36  	results := decideWith(nil, nil, []fileInfo{fileA, fileB, fileC, fileD}, 10)
    37  	if len(results) != 0 {
    38  		t.Fatalf("Expected 0 results. Actual: %d", len(results))
    39  	}
    40  }
    41  
    42  func TestThreeCommands(t *testing.T) {
    43  	results := decideWith([]WatCommand{cmdA, cmdB, cmdC}, nil, []fileInfo{fileA, fileB, fileC, fileD}, 3)
    44  	expected := []WatCommand{cmdA, cmdB, cmdC}
    45  	if !reflect.DeepEqual(results, expected) {
    46  		t.Fatalf("Expected %+v. Actual: %+v", expected, results)
    47  	}
    48  }
    49  
    50  func TestThreeCommandsToOne(t *testing.T) {
    51  	results := decideWith([]WatCommand{cmdD, cmdB, cmdC}, nil, []fileInfo{fileA, fileB, fileC, fileD}, 1)
    52  	expected := []WatCommand{cmdB}
    53  	if !reflect.DeepEqual(results, expected) {
    54  		t.Fatalf("Expected %+v. Actual: %+v", expected, results)
    55  	}
    56  }
    57  
    58  func TestThreeCommandsOutOfOrder(t *testing.T) {
    59  	results := decideWith([]WatCommand{cmdC, cmdA, cmdB}, nil, []fileInfo{fileA, fileB, fileC, fileD}, 3)
    60  	expected := []WatCommand{cmdA, cmdB, cmdC}
    61  	if !reflect.DeepEqual(results, expected) {
    62  		t.Fatalf("Expected %+v. Actual: %+v", expected, results)
    63  	}
    64  }
    65  
    66  func TestMissingFiles(t *testing.T) {
    67  	results := decideWith([]WatCommand{cmdA, cmdB, cmdC}, nil, []fileInfo{fileB}, 3)
    68  	expected := []WatCommand{cmdB, cmdA, cmdC}
    69  	if !reflect.DeepEqual(results, expected) {
    70  		t.Fatalf("Expected %+v. Actual: %+v", expected, results)
    71  	}
    72  }
    73  
    74  func TestCost(t *testing.T) {
    75  	ds := newDecisionStore()
    76  	if ds.HasCost(cmdA) {
    77  		t.Fatalf("Expected HasCost: false")
    78  	}
    79  
    80  	ds.addCommandCost(CommandLog{Command: cmdA.Command, Duration: time.Minute}, LogContext{})
    81  	if !ds.HasCost(cmdA) {
    82  		t.Fatalf("Expected HasCost: true")
    83  	}
    84  
    85  	if ds.Cost(cmdA) != time.Minute {
    86  		t.Errorf("Expected cost %s. Actual %s", time.Minute, ds.Cost(cmdA))
    87  	}
    88  
    89  	ds.addCommandCost(CommandLog{Command: cmdA.Command, Duration: time.Second}, LogContext{})
    90  	expected := time.Duration(0.3*float64(time.Minute.Nanoseconds()) + 0.7*float64(time.Second.Nanoseconds()))
    91  	if ds.Cost(cmdA) != expected {
    92  		t.Errorf("Expected cost %s. Actual %s", expected, ds.Cost(cmdA))
    93  	}
    94  }
    95  
    96  func TestFailureDecide(t *testing.T) {
    97  	group := CommandLogGroup{
    98  		Logs: []CommandLog{
    99  			cmdLogSecFailA,
   100  			cmdLogSecFailA,
   101  			cmdLogSecSuccessB,
   102  			cmdLogSecFailB,
   103  			cmdLogSecFailC,
   104  			cmdLogSecFailC,
   105  			cmdLogSecSuccessD,
   106  			cmdLogSecFailD,
   107  		},
   108  		Context: LogContext{StartTime: time.Now(), Source: LogSourceUser},
   109  	}
   110  	results := decideWith([]WatCommand{cmdA, cmdB, cmdC, cmdD}, []CommandLogGroup{group}, nil, 3)
   111  	expected := []WatCommand{cmdA, cmdC, cmdB}
   112  	if !reflect.DeepEqual(results, expected) {
   113  		t.Fatalf("Expected %+v. Actual: %+v", expected, results)
   114  	}
   115  }
   116  
   117  func TestCostSensitiveGainDecide(t *testing.T) {
   118  	group := CommandLogGroup{
   119  		Logs: []CommandLog{
   120  			// Because cmdA takes a minute, even though it fails all the time,
   121  			// it becomes the worst choice.
   122  			cmdLogMinuteFailA,
   123  			cmdLogMinuteFailA,
   124  			cmdLogSecSuccessB,
   125  			cmdLogSecFailB,
   126  			cmdLogSecFailC,
   127  			cmdLogSecFailC,
   128  			cmdLogSecSuccessD,
   129  			cmdLogSecFailD,
   130  		},
   131  		Context: LogContext{StartTime: time.Now(), Source: LogSourceUser},
   132  	}
   133  	results := decideWith([]WatCommand{cmdA, cmdB, cmdC, cmdD}, []CommandLogGroup{group}, nil, 3)
   134  	expected := []WatCommand{cmdC, cmdB, cmdD}
   135  	if !reflect.DeepEqual(results, expected) {
   136  		t.Fatalf("Expected %+v. Actual: %+v", expected, results)
   137  	}
   138  }
   139  
   140  func TestCorrelationSensitiveGainDecide(t *testing.T) {
   141  	// Create groups where A + B are highly correlated,
   142  	// so we will not choose B if A is the first task.
   143  	group1 := CommandLogGroup{
   144  		Logs: []CommandLog{
   145  			cmdLogSecSuccessA,
   146  			cmdLogSecSuccessB,
   147  			cmdLogSecSuccessC,
   148  		},
   149  		Context: LogContext{StartTime: time.Now(), Source: LogSourceUser},
   150  	}
   151  	group2 := CommandLogGroup{
   152  		Logs: []CommandLog{
   153  			cmdLogSecSuccessA,
   154  			cmdLogSecSuccessB,
   155  			cmdLogSecFailC,
   156  		},
   157  		Context: LogContext{StartTime: time.Now(), Source: LogSourceUser},
   158  	}
   159  	group3 := CommandLogGroup{
   160  		Logs: []CommandLog{
   161  			cmdLogSecFailA,
   162  			cmdLogSecFailB,
   163  		},
   164  		Context: LogContext{StartTime: time.Now(), Source: LogSourceUser},
   165  	}
   166  
   167  	results := decideWith([]WatCommand{cmdA, cmdC, cmdB},
   168  		[]CommandLogGroup{group1, group2, group3, group3, group3}, nil, 3)
   169  	expected := []WatCommand{cmdA, cmdC, cmdB}
   170  	if !reflect.DeepEqual(results, expected) {
   171  		t.Fatalf("Expected %+v. Actual: %+v", expected, results)
   172  	}
   173  }
   174  
   175  func TestFailureProbabilityDifferentPackage(t *testing.T) {
   176  	ds := newDecisionStore()
   177  
   178  	condB := Condition{EditedFile: "b.txt"}
   179  	prob := ds.FailureProbability(cmdA, condB)
   180  	expected := 0.5
   181  	if !roughlyEqual(prob, expected) {
   182  		t.Fatalf("Expected %v, actual: %v", expected, prob)
   183  	}
   184  
   185  	ctx := LogContext{}
   186  	ds.addCommandHistory(cmdLogA, ctx, Condition{})
   187  	prob = ds.FailureProbability(cmdA, condB)
   188  	expected = failProbabilityZeroCase / (1 + failProbabilityZeroCase)
   189  	if !roughlyEqual(prob, expected) {
   190  		t.Fatalf("Expected %v, actual: %v", expected, prob)
   191  	}
   192  
   193  	ds.addCommandHistory(cmdLogSecFailA, ctx, Condition{})
   194  	prob = ds.FailureProbability(cmdA, condB)
   195  	expected = 0.5
   196  	if !roughlyEqual(prob, expected) {
   197  		t.Fatalf("Expected %v, actual: %v", expected, prob)
   198  	}
   199  
   200  	ds.addCommandHistory(cmdLogSecFailA, ctx, Condition{})
   201  	prob = ds.FailureProbability(cmdA, condB)
   202  	expected = 0.666
   203  	if !roughlyEqual(prob, expected) {
   204  		t.Fatalf("Expected %v, actual: %v", expected, prob)
   205  	}
   206  
   207  	// Logs that are sensitive to the most recently edited file
   208  	// make the narrower probability kick in.
   209  	ds.addCommandHistory(cmdLogA, LogContext{RecentEdits: []string{"b.txt"}}, Condition{})
   210  	prob = ds.FailureProbability(cmdA, condB)
   211  	expected = failProbabilityZeroCase / (1 + failProbabilityZeroCase)
   212  	if !roughlyEqual(prob, expected) {
   213  		t.Fatalf("Expected %v, actual: %v", expected, prob)
   214  	}
   215  }
   216  
   217  func TestFailureProbabilitySamePackage(t *testing.T) {
   218  	ds := newDecisionStore()
   219  
   220  	condA := Condition{EditedFile: "a.txt"}
   221  	prob := ds.FailureProbability(cmdA, condA)
   222  	expected := 0.5
   223  	if !roughlyEqual(prob, expected) {
   224  		t.Fatalf("Expected %v, actual: %v", expected, prob)
   225  	}
   226  
   227  	ctx := LogContext{}
   228  	ds.addCommandHistory(cmdLogA, ctx, Condition{})
   229  	prob = ds.FailureProbability(cmdA, condA)
   230  	expected = 0.5
   231  	if !roughlyEqual(prob, expected) {
   232  		t.Fatalf("Expected %v, actual: %v", expected, prob)
   233  	}
   234  
   235  	ds.addCommandHistory(cmdLogSecFailA, ctx, Condition{})
   236  	prob = ds.FailureProbability(cmdA, condA)
   237  	expected = 0.5
   238  	if !roughlyEqual(prob, expected) {
   239  		t.Fatalf("Expected %v, actual: %v", expected, prob)
   240  	}
   241  
   242  	ds.addCommandHistory(cmdLogSecFailA, ctx, Condition{})
   243  	prob = ds.FailureProbability(cmdA, condA)
   244  	expected = 0.666
   245  	if !roughlyEqual(prob, expected) {
   246  		t.Fatalf("Expected %v, actual: %v", expected, prob)
   247  	}
   248  }
   249  
   250  func TestCostDecide(t *testing.T) {
   251  	// Create log data that says D is faster than B, and only has data for B+D
   252  	ds := newDecisionStore()
   253  	ds.addCommandCost(cmdLogB, LogContext{})
   254  	ds.addCommandCost(cmdLogD, LogContext{})
   255  	results := secondTierDecideWith([]WatCommand{cmdA, cmdB, cmdC, cmdD}, ds, nil, 3)
   256  	expected := []WatCommand{cmdD, cmdB, cmdA}
   257  	if !reflect.DeepEqual(results, expected) {
   258  		t.Fatalf("Expected %+v. Actual: %+v", expected, results)
   259  	}
   260  }
   261  
   262  func roughlyEqual(a, b float64) bool {
   263  	diff := math.Abs(a - b)
   264  	return diff < 0.001
   265  }
   266  
   267  func newTestLog(cmd WatCommand, dur time.Duration, success bool) CommandLog {
   268  	return CommandLog{
   269  		Command:  cmd.Command,
   270  		Success:  success,
   271  		Duration: dur,
   272  	}
   273  }