blob: 83e46bf3decde9cf1f5a0100e4a15662fa86b358 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Testutil.go implements common functionality used in testing both MemStore and
// PersistentStore interfaces.
package storage
import (
"strconv"
"testing"
"util"
"github.com/golang/protobuf/proto"
"cobalt"
"shuffler"
)
var r = util.NewDeterministicRandom(int64(1))
// NewObservationMetaData constructs fake observation metadata for testing.
func NewObservationMetaData(testID int) *cobalt.ObservationMetadata {
id := uint32(testID)
return &cobalt.ObservationMetadata{
CustomerId: id,
ProjectId: id,
MetricId: id,
DayIndex: id,
}
}
// MakeRandomObservationVals constructs a list of fake |ObservationVal|s for
// testing.
func MakeRandomObservationVals(numMsgs int) []*shuffler.ObservationVal {
var vals []*shuffler.ObservationVal
eMsgList := MakeRandomEncryptedMsgs(numMsgs)
for i := 0; i < numMsgs; i++ {
vals = append(vals, NewObservationVal(eMsgList[i], strconv.Itoa(i), 999))
}
return vals
}
// MakeRandomEncryptedMsgs returns a list of random |EncryptedMessages| using
// the default Scheme and Analyzer's public key hash.
func MakeRandomEncryptedMsgs(numMsgs int) []*cobalt.EncryptedMessage {
var eMsgList []*cobalt.EncryptedMessage
for i := 0; i < numMsgs; i++ {
bytes, _ := r.RandomBytes(10)
eMsgList = append(eMsgList, &cobalt.EncryptedMessage{
Ciphertext: bytes,
})
}
return eMsgList
}
// NewObservationBatchForMetadata creates a random |ObservationBatch| for the
// given metadata |om|.
func NewObservationBatchForMetadata(om *cobalt.ObservationMetadata, numMsgs int) *cobalt.ObservationBatch {
return &cobalt.ObservationBatch{
MetaData: om,
EncryptedObservation: MakeRandomEncryptedMsgs(numMsgs),
}
}
// MakeObservationBatches generates a set of ObservationBatches of size
// |numBatches|. For each i, batch i will have an ObservationMetadata that uses
// i for all IDs and will contain i encrypted observations.
func MakeObservationBatches(numBatches int) []*cobalt.ObservationBatch {
var batches []*cobalt.ObservationBatch
for i := 1; i <= numBatches; i++ {
batches = append(batches, NewObservationBatchForMetadata(NewObservationMetaData(i), i))
}
return batches
}
// CheckKeys tests if the total count of ObservationMetadata keys saved in
// store is equal to |expectedNumKeys| and the contents of the keys match with
// the given |expectedKeys| list.
func CheckKeys(t *testing.T, store Store, expectedKeys []*cobalt.ObservationMetadata) {
if store == nil {
panic("store is nil")
}
gotKeys, err := store.GetKeys()
if err != nil {
t.Errorf("GetKeys: got keys [%v] with error: %v, expected empty list", gotKeys, err)
}
if len(gotKeys) != len(expectedKeys) {
t.Errorf("GetKeys: got [%d] keys, expected [%d] keys", gotKeys, len(expectedKeys))
}
gotKeysSet := make(map[string]bool, len(gotKeys))
for _, key := range gotKeys {
gotKeysSet[proto.CompactTextString(key)] = true
}
for _, key := range expectedKeys {
_, ok := gotKeysSet[proto.CompactTextString(key)]
if !ok {
t.Errorf("got keys [%v], expected key [%v] to be present in the resultset", gotKeysSet, key)
return
}
}
}
// CheckNumObservations tests if the total count of observations returned by
// GetNumObservations() call for a given ObservationMetadata |om| key is equal
// to |expectedNumObs|.
func CheckNumObservations(t *testing.T, store Store, om *cobalt.ObservationMetadata, expectedNumObs int) {
if store == nil {
panic("store is nil")
}
if om == nil {
panic("Metadata is nil")
}
if obValsLen, err := store.GetNumObservations(om); err != nil && expectedNumObs != 0 {
t.Errorf("GetNumObservations: got error [%v] for metadata [%v]", err, om)
} else if obValsLen != expectedNumObs {
t.Errorf("GetNumObservations: got [%d] ObservationVals, expected [%d] ObservationVals per metadata [%v]", obValsLen, expectedNumObs, om)
}
}
// CheckObservations tests if the total count of observations returned by
// GetObservations() for a given ObservationMetadata |om| key is equal to
// |expectedNumObs|, and returns the fetched list of |ObservationVal|s.
func CheckObservations(t *testing.T, store Store, om *cobalt.ObservationMetadata, expectedNumObs int) []*shuffler.ObservationVal {
if store == nil {
panic("store is nil")
}
if om == nil {
panic("Metadata is nil")
}
iter, err := store.GetObservations(om)
if err != nil {
t.Errorf("GetObservations: got error %v for metadata [%v]", err, om)
}
if iter == nil {
t.Errorf("GetObservations: got empty iterator for metadata [%v]", om)
}
var gotObVals []*shuffler.ObservationVal
for iter.Next() {
obVal, iErr := iter.Get()
if iErr != nil {
t.Errorf("got error on iter.Get() for key [%v]: %v", om, err)
}
gotObVals = append(gotObVals, obVal)
}
if err := iter.Release(); err != nil {
t.Errorf("got error on iter.Release() for metadata [%v]: %v", om, err)
}
if len(gotObVals) != expectedNumObs {
t.Errorf("GetObservations: got [%d] observations, expected [%d] observations for metadata [%v]", len(gotObVals), expectedNumObs, om)
}
return gotObVals
}
// CheckGetObservations tests if the observations fetched from store for a given
// ObservationMetadata |om| key are valid. Observations are deemed valid if and
// only if:
// - The total count of observations returned by GetObservations() is equal to
// length of |expectedEncMsgs|, and
// - The contents of stored |EncryptedMessages|s match with the given
// |expectedEncMsgs| list.
func CheckGetObservations(t *testing.T, store Store, om *cobalt.ObservationMetadata, expectedEncMsgs []*cobalt.EncryptedMessage) {
gotObVals := CheckObservations(t, store, om, len(expectedEncMsgs))
if gotObVals == nil && len(expectedEncMsgs) != 0 {
t.Errorf("GetObservations() call failed for key: [%v]", om)
return
}
gotEMsgSet := make(map[string]bool, len(gotObVals))
for _, obVal := range gotObVals {
gotEMsgSet[proto.CompactTextString(obVal.EncryptedObservation)] = true
}
for _, eMsg := range expectedEncMsgs {
_, ok := gotEMsgSet[proto.CompactTextString(eMsg)]
if !ok {
t.Errorf("got [%v], expected encrypted message [%v] for metadata [%v] to be present in the resultset", gotEMsgSet, eMsg, om)
return
}
}
}
// CheckDeleteObservations tests if the observations fetched from store for a
// given ObservationMetadata |om| key are valid after a successful
// DeleteValues() call, if and only if:
// - The total count of observations returned by GetObservations() is equal to
// |expectedNumObs|, and
// - The fetched observations doesn't contain any ObservationVal from the
// |expectedDeleteVals| list.
func CheckDeleteObservations(t *testing.T, store Store, om *cobalt.ObservationMetadata, expectedNumObs int, expectedDeleteVals []*shuffler.ObservationVal) {
gotObVals := CheckObservations(t, store, om, expectedNumObs)
if gotObVals == nil && expectedNumObs != 0 {
t.Errorf("GetObservations() call failed for key: [%v]", om)
return
}
for _, obVal := range expectedDeleteVals {
for _, gotObVal := range gotObVals {
if obVal.Id == gotObVal.Id {
t.Errorf("Observation val [%v] still exists after delete() call for metadata [%v] with vals: [%v]", obVal, om, gotObVals)
return
}
}
}
}
// CheckIterator tests if the given |iter| is valid and then fetches all the
// ObservationVals using |iter| until the iterator gets exhausted.
func CheckIterator(t *testing.T, iter Iterator) []*shuffler.ObservationVal {
var gotObVals []*shuffler.ObservationVal
if iter == nil {
return gotObVals
}
for iter.Next() {
val, err := iter.Get()
if err != nil {
t.Errorf("iter.Get() returned error: %v", err)
}
gotObVals = append(gotObVals, val)
}
err := iter.Release()
if err != nil {
t.Errorf("got error while releasing iterator: %v", err)
}
if iter.Next() {
t.Errorf("iterator must be empty after release()")
}
return gotObVals
}
// ResetStoreForTesting clears any in-memory caches, and deletes all data
// in the store if |destroy| is true. For MemStore, data gets destroyed
// irrespective of the |destroy| value.
func ResetStoreForTesting(store Store, destroy bool) {
switch s := store.(type) {
case *MemStore:
s.Reset()
case *LevelDBStore:
s.Reset(destroy)
default:
panic("unsupported store type")
}
}