// See ExpectationsStore interface. func (s *SQLExpectationsStore) Get() (exp *Expectations, err error) { // Load the newest record from the database. const stmt = `SELECT t1.name, t1.digest, t1.label FROM exp_test_change AS t1 JOIN ( SELECT name, digest, MAX(changeid) as changeid FROM exp_test_change GROUP BY name, digest ) AS t2 ON (t1.name = t2.name AND t1.digest = t2.digest AND t1.changeid = t2.changeid) WHERE t1.removed IS NULL` rows, err := s.vdb.DB.Query(stmt) if err != nil { return nil, err } defer util.Close(rows) result := map[string]types.TestClassification{} for rows.Next() { var testName, digest, label string if err = rows.Scan(&testName, &digest, &label); err != nil { return nil, err } if _, ok := result[testName]; !ok { result[testName] = types.TestClassification(map[string]types.Label{}) } result[testName][digest] = types.LabelFromString(label) } return &Expectations{ Tests: result, }, nil }
// Test against the expectation store interface. func testExpectationStore(t *testing.T, store ExpectationsStore, eventBus *eventbus.EventBus) { // Get the initial log size. This is necessary because we // call this function multiple times with the same underlying // SQLExpectationStore. initialLogRecs, initialLogTotal, err := store.QueryLog(0, 100, true) assert.Nil(t, err) initialLogRecsLen := len(initialLogRecs) // If we have an event bus then keep gathering events. callbackCh := make(chan []string, 3) if eventBus != nil { eventBus.SubscribeAsync(EV_EXPSTORAGE_CHANGED, func(e interface{}) { testNames := append([]string{}, e.([]string)...) sort.Strings(testNames) callbackCh <- testNames }) } TEST_1, TEST_2 := "test1", "test2" // digests DIGEST_11, DIGEST_12 := "d11", "d12" DIGEST_21, DIGEST_22 := "d21", "d22" expChange_1 := map[string]types.TestClassification{ TEST_1: types.TestClassification{ DIGEST_11: types.POSITIVE, DIGEST_12: types.NEGATIVE, }, TEST_2: types.TestClassification{ DIGEST_21: types.POSITIVE, DIGEST_22: types.NEGATIVE, }, } logEntry_1 := []*TriageDetail{ &TriageDetail{TEST_1, DIGEST_11, "positive"}, &TriageDetail{TEST_1, DIGEST_12, "negative"}, &TriageDetail{TEST_2, DIGEST_21, "positive"}, &TriageDetail{TEST_2, DIGEST_22, "negative"}, } assert.Nil(t, store.AddChange(expChange_1, "user-0")) if eventBus != nil { eventBus.Wait(EV_EXPSTORAGE_CHANGED) assert.Equal(t, 1, len(callbackCh)) assert.Equal(t, []string{TEST_1, TEST_2}, <-callbackCh) } foundExps, err := store.Get() assert.Nil(t, err) assert.Equal(t, expChange_1, foundExps.Tests) assert.False(t, &expChange_1 == &foundExps.Tests) checkLogEntry(t, store, expChange_1) // Update digests. expChange_2 := map[string]types.TestClassification{ TEST_1: types.TestClassification{ DIGEST_11: types.NEGATIVE, }, TEST_2: types.TestClassification{ DIGEST_22: types.UNTRIAGED, }, } logEntry_2 := []*TriageDetail{ &TriageDetail{TEST_1, DIGEST_11, "negative"}, &TriageDetail{TEST_2, DIGEST_22, "untriaged"}, } assert.Nil(t, store.AddChange(expChange_2, "user-1")) if eventBus != nil { eventBus.Wait(EV_EXPSTORAGE_CHANGED) assert.Equal(t, 1, len(callbackCh)) assert.Equal(t, []string{TEST_1, TEST_2}, <-callbackCh) } foundExps, err = store.Get() assert.Nil(t, err) assert.Equal(t, types.NEGATIVE, foundExps.Tests[TEST_1][DIGEST_11]) assert.Equal(t, types.UNTRIAGED, foundExps.Tests[TEST_2][DIGEST_22]) checkLogEntry(t, store, expChange_2) // Send empty changes to test the event bus. emptyChanges := map[string]types.TestClassification{} assert.Nil(t, store.AddChange(emptyChanges, "user-2")) if eventBus != nil { eventBus.Wait(EV_EXPSTORAGE_CHANGED) assert.Equal(t, 1, len(callbackCh)) assert.Equal(t, []string{}, <-callbackCh) } checkLogEntry(t, store, emptyChanges) // Remove digests. removeDigests_1 := map[string][]string{ TEST_1: []string{DIGEST_11}, TEST_2: []string{DIGEST_22}, } assert.Nil(t, store.RemoveChange(removeDigests_1)) if eventBus != nil { eventBus.Wait(EV_EXPSTORAGE_CHANGED) assert.Equal(t, 1, len(callbackCh)) assert.Equal(t, []string{TEST_1, TEST_2}, <-callbackCh) } foundExps, err = store.Get() assert.Nil(t, err) assert.Equal(t, types.TestClassification(map[string]types.Label{DIGEST_12: types.NEGATIVE}), foundExps.Tests[TEST_1]) assert.Equal(t, types.TestClassification(map[string]types.Label{DIGEST_21: types.POSITIVE}), foundExps.Tests[TEST_2]) removeDigests_2 := map[string][]string{TEST_1: []string{DIGEST_12}} assert.Nil(t, store.RemoveChange(removeDigests_2)) if eventBus != nil { eventBus.Wait(EV_EXPSTORAGE_CHANGED) assert.Equal(t, 1, len(callbackCh)) assert.Equal(t, []string{TEST_1}, <-callbackCh) } foundExps, err = store.Get() assert.Nil(t, err) assert.Equal(t, 1, len(foundExps.Tests)) assert.Nil(t, store.RemoveChange(map[string][]string{})) if eventBus != nil { eventBus.Wait(EV_EXPSTORAGE_CHANGED) assert.Equal(t, 1, len(callbackCh)) assert.Equal(t, []string{}, <-callbackCh) } // Make sure we added the correct number of triage log entries. addedRecs := 3 logEntries, total, err := store.QueryLog(0, 5, true) assert.Nil(t, err) assert.Equal(t, addedRecs+initialLogTotal, total) assert.Equal(t, util.MinInt(addedRecs+initialLogRecsLen, 5), len(logEntries)) lastRec := logEntries[0] secondToLastRec := logEntries[1] assert.Equal(t, 0, len(logEntries[0].Details)) assert.Equal(t, logEntry_2, logEntries[1].Details) assert.Equal(t, logEntry_1, logEntries[2].Details) logEntries, total, err = store.QueryLog(100, 5, true) assert.Nil(t, err) assert.Equal(t, addedRecs+initialLogTotal, total) assert.Equal(t, 0, len(logEntries)) // Undo the latest version and make sure the corresponding record is correct. changes, err := store.UndoChange(lastRec.ID, "user-1") assert.Nil(t, err) checkLogEntry(t, store, changes) changes, err = store.UndoChange(secondToLastRec.ID, "user-1") assert.Nil(t, err) checkLogEntry(t, store, changes) addedRecs += 2 logEntries, total, err = store.QueryLog(0, 2, true) assert.Nil(t, err) assert.Equal(t, addedRecs+initialLogTotal, total) assert.Equal(t, 0, len(logEntries[1].Details)) assert.Equal(t, 2, len(logEntries[0].Details)) foundExps, err = store.Get() assert.Nil(t, err) for testName, digests := range expChange_2 { for d := range digests { _, ok := foundExps.Tests[testName][d] assert.True(t, ok) assert.Equal(t, expChange_1[testName][d].String(), foundExps.Tests[testName][d].String()) } } // Make sure undoing the previous undo causes an error. logEntries, _, err = store.QueryLog(0, 1, false) assert.Nil(t, err) assert.Equal(t, 1, len(logEntries)) _, err = store.UndoChange(logEntries[0].ID, "user-1") assert.NotNil(t, err) // Make sure getExpectationsAt works correctly. sqlStore, ok := store.(*SQLExpectationsStore) if ok { logEntries, _, err = store.QueryLog(0, 100, true) assert.Nil(t, err) // Check the first addition. firstAdd := logEntries[len(logEntries)-1] secondAdd := logEntries[len(logEntries)-2] secondUndo := logEntries[len(logEntries)-5] checkExpectationsAt(t, sqlStore, firstAdd, "first") checkExpectationsAt(t, sqlStore, secondAdd, "second") checkExpectationsAt(t, sqlStore, secondUndo, "third") } }