blob: 846781d3ef6e185d603d594cf0794dbaae57b2a8 [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.
#ifndef COBALT_ANALYZER_STORE_DATA_STORE_TEST_H_
#define COBALT_ANALYZER_STORE_DATA_STORE_TEST_H_
#include "analyzer/store/data_store.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "gflags/gflags.h"
#include "glog/logging.h"
#include "third_party/googletest/googletest/include/gtest/gtest.h"
// This file contains type-parameterized tests of the DataStore interface.
//
// We use C++ templates along with the macros TYPED_TEST_CASE_P and
// TYPED_TEST_P in order to define test templates that may be instantiated to
// to produce concrete tests of various implementations of the DataStore
// interface.
//
// See memory_store_test.cc and bigtable_store_emulator_test.cc for the
// concrete instantiations.
//
// NOTE: If you add a new test to this file you must add its name to the
// invocation REGISTER_TYPED_TEST_CASE_P macro at the bottom of this file.
namespace cobalt {
namespace analyzer {
namespace store {
static const int kNumColumns = 3;
// DataStoreTest is templatized on the parameter |StoreFactoryClass| which
// must be the name of a class that contains the following method:
// static DataStore* NewStore()
// See MemoryStoreFactory in memory_store_test_helper.h for example.
//
// Note: For simplicity we perform all tests using the Observations table
// only.
template <class StoreFactoryClass>
class DataStoreTest : public ::testing::Test {
protected:
DataStoreTest() : data_store_(StoreFactoryClass::NewStore()) {}
void SetUp() {
EXPECT_EQ(kOK, data_store_->DeleteAllRows(DataStore::kObservations));
EXPECT_EQ(0u, GetNumRows());
}
// Adds |num_rows| rows with kNumColumns columns each.
void AddRows(int num_rows);
// Returns the total number of rows in the store.
size_t GetNumRows();
// Reads the specified number of columns from the specified row and checks
// that the result is as expected.
//
// If num_columns = 0 then no columns are specified in the read and therefore
// all columns should be returned and so the expected num_columns is
// kNumColumns.
void ReadSingleRowAndCheck(int num_columns, int row_idnex,
bool expect_row_found);
// Reads the specified number of columns from the specified row range
// and checks that the results are as expected.
//
// If num_columns = 0 then no columns are specified in the read and therefore
// all columns should be returned and so the expected num_columns is
// kNumColumns.
//
// Set limit_row = -1 to indicate an unbounded range.
void ReadRowsAndCheck(int num_columns, int start_row, bool inclusive,
int limit_row, int max_rows, size_t expected_num_rows,
bool expect_more_available);
void DeleteRowsWithPrefix(int basis, int suffix_length);
// In order to work around the following bug in the Bigtable Emulator
// https://github.com/GoogleCloudPlatform/google-cloud-go/issues/489
// we use a different set of row keys for each test. Each row created during
// a test will be prefixed with |test_prefix|.
void set_test_prefix(std::string test_prefix) {
test_prefix_ = std::move(test_prefix);
}
// Generates a row key string based on the given |index| and prefix.
static std::string RowKeyString(const std::string& prefix, uint32_t index) {
// We allocate a buffer of size 14 to leave room for the trailing null.
std::string out(14, 0);
std::snprintf(&out[0], out.size(), "row%.10u", index);
// Discard the trailing null.
out.resize(13);
return prefix + out;
}
// Generates a column name string based on the given |colum_index|.
static std::string ColumnNameString(uint32_t column_index) {
std::string out(17, 0);
std::snprintf(&out[0], out.size(), "column%.10u", column_index);
return out;
}
// Generates a value string based on the given |row_index| and |colum_index|.
static std::string ValueString(uint32_t row_index, uint32_t column_index) {
std::string out(27, 0);
std::snprintf(&out[0], out.size(), "value%.10u:%.10u", row_index,
column_index);
return out;
}
// Makes a vector of column name strings for |num_columns| columns.
static std::vector<std::string> MakeColumnNames(size_t num_columns) {
std::vector<std::string> column_names;
for (size_t column_index = 0; column_index < num_columns; column_index++) {
column_names.push_back(ColumnNameString(column_index));
}
return column_names;
}
std::unique_ptr<DataStore> data_store_;
std::string test_prefix_;
};
template <class StoreFactoryClass>
void DataStoreTest<StoreFactoryClass>::AddRows(int num_rows) {
std::vector<std::string> column_names = MakeColumnNames(kNumColumns);
std::vector<DataStore::Row> rows;
for (int row_index = 0; row_index < num_rows; row_index++) {
DataStore::Row row;
row.key = RowKeyString(test_prefix_, row_index);
for (int column_index = 0; column_index < kNumColumns; column_index++) {
row.column_values[column_names[column_index]] =
ValueString(row_index, column_index);
}
rows.emplace_back(std::move(row));
}
EXPECT_EQ(kOK,
data_store_->WriteRows(DataStore::kObservations, std::move(rows)));
}
template <class StoreFactoryClass>
size_t DataStoreTest<StoreFactoryClass>::GetNumRows() {
std::vector<std::string> column_names;
DataStore::ReadResponse read_response = data_store_->ReadRows(
DataStore::kObservations, RowKeyString(test_prefix_, 0), true, "",
column_names, UINT32_MAX);
EXPECT_EQ(kOK, read_response.status);
return read_response.rows.size();
}
template <class StoreFactoryClass>
void DataStoreTest<StoreFactoryClass>::ReadSingleRowAndCheck(
int num_columns, int row_index, bool expect_row_found) {
std::vector<std::string> column_names = MakeColumnNames(num_columns);
DataStore::Row row;
row.key = RowKeyString(test_prefix_, row_index);
auto status =
data_store_->ReadRow(DataStore::kObservations, column_names, &row);
if (expect_row_found) {
ASSERT_EQ(kOK, status);
} else {
EXPECT_EQ(kNotFound, status);
return;
}
int expected_num_columns = (num_columns == 0 ? kNumColumns : num_columns);
for (int column_index = 0; column_index < expected_num_columns;
column_index++) {
EXPECT_EQ(ValueString(row_index, column_index),
row.column_values.at(ColumnNameString(column_index)));
}
}
template <class StoreFactoryClass>
void DataStoreTest<StoreFactoryClass>::ReadRowsAndCheck(
int num_columns, int start_row, bool inclusive, int limit_row, int max_rows,
size_t expected_num_rows, bool expect_more_available) {
std::vector<std::string> column_names = MakeColumnNames(num_columns);
std::string limit_row_key;
if (limit_row < 0) {
limit_row_key = "";
} else {
limit_row_key = RowKeyString(test_prefix_, limit_row);
}
DataStore::ReadResponse read_response = data_store_->ReadRows(
DataStore::kObservations, RowKeyString(test_prefix_, start_row),
inclusive, limit_row_key, column_names, max_rows);
EXPECT_EQ(kOK, read_response.status);
EXPECT_EQ(expected_num_rows, read_response.rows.size());
size_t expected_num_columns = (num_columns == 0 ? kNumColumns : num_columns);
int row_index = (inclusive ? start_row : start_row + 1);
for (const DataStore::Row& row : read_response.rows) {
EXPECT_EQ(RowKeyString(test_prefix_, row_index), row.key);
EXPECT_EQ(expected_num_columns, row.column_values.size());
for (size_t column_index = 0; column_index < expected_num_columns;
column_index++) {
EXPECT_EQ(ValueString(row_index, column_index),
row.column_values.at(ColumnNameString(column_index)));
}
row_index++;
}
EXPECT_EQ(expect_more_available, read_response.more_available);
}
template <class StoreFactoryClass>
void DataStoreTest<StoreFactoryClass>::DeleteRowsWithPrefix(int basis,
int suffix_length) {
std::string prefix = RowKeyString(test_prefix_, basis);
prefix.resize(prefix.size() - suffix_length);
Status status =
data_store_->DeleteRowsWithPrefix(DataStore::kObservations, prefix);
EXPECT_EQ(kOK, status);
}
TYPED_TEST_CASE_P(DataStoreTest);
TYPED_TEST_P(DataStoreTest, WriteAndReadRows) {
// Add 3000 rows of 3 columns each.
this->AddRows(3000);
// Read row number 0, expect it to exist.
this->ReadSingleRowAndCheck(kNumColumns, 0, true);
// Read row number 1234, expect it to exist.
this->ReadSingleRowAndCheck(kNumColumns, 1234, true);
// Read row number 2999, expect it to exist.
this->ReadSingleRowAndCheck(kNumColumns, 2999, true);
// Read row number 3000, expect it to not exist.
this->ReadSingleRowAndCheck(kNumColumns, 3000, false);
// Read rows [100, 175) with max_rows = 50. Expect 50 rows with more
// available.
size_t max_rows = 50;
size_t expected_rows = 50;
this->ReadRowsAndCheck(kNumColumns, 100, true, 175, max_rows, expected_rows,
true);
// Read rows (100, 175) with max_rows = 50. Expect 50 rows with more
// available.
this->ReadRowsAndCheck(kNumColumns, 100, false, 175, max_rows, expected_rows,
true);
// Read rows [100, 175) with max_rows = 80. Expect 75 rows with no more
// available.
max_rows = 80;
expected_rows = 75;
this->ReadRowsAndCheck(kNumColumns, 100, true, 175, max_rows, expected_rows,
false);
// Read rows (100, 175) with max_rows = 80. Expect 74 rows with no more
// available.
max_rows = 80;
expected_rows = 74;
this->ReadRowsAndCheck(kNumColumns, 100, false, 175, max_rows, expected_rows,
false);
// Read rows [100, 2100) with max_rows = 100. Expect 100 rows with
// more available.
max_rows = 100;
expected_rows = 100;
this->ReadRowsAndCheck(kNumColumns, 100, true, 2100, max_rows, expected_rows,
true);
// Read rows (100, 2100) with max_rows = 100. Expect 100 rows with
// more available.
this->ReadRowsAndCheck(kNumColumns, 100, false, 2100, max_rows, expected_rows,
true);
// Read rows (100, 2100) with max_rows = UINT32_MAX. Expect 1999 rows with
// no more available.
max_rows = UINT32_MAX;
expected_rows = 1999;
this->ReadRowsAndCheck(kNumColumns, 100, false, 2100, max_rows, expected_rows,
false);
// Read rows [0, 1) with max_rows = 100. Expect 1 row with
// more no available.
max_rows = 100;
expected_rows = 1;
this->ReadRowsAndCheck(kNumColumns, 0, true, 1, max_rows, expected_rows,
false);
}
// Tests reading an unbounded range.
TYPED_TEST_P(DataStoreTest, UnboundedRange) {
this->set_test_prefix("UnboundedRange");
// Add 1000 rows of 3 columns each.
this->AddRows(1000);
ASSERT_EQ(1000u, this->GetNumRows());
// Read rows [100, infinity) with max_rows = 50. Expect 50 rows with more
// available.
size_t max_rows = 50;
size_t expected_rows = 50;
this->ReadRowsAndCheck(kNumColumns, 100, true, -1, max_rows, expected_rows,
true);
// Read rows (100, infinity) with max_rows = 50. Expect 50 rows with more
// available.
this->ReadRowsAndCheck(kNumColumns, 100, false, -1, max_rows, expected_rows,
true);
// Read rows [100, infinity) with max_rows = 100. Expect 100 rows with
// more available.
max_rows = 100;
expected_rows = 100;
this->ReadRowsAndCheck(kNumColumns, 100, true, -1, max_rows, expected_rows,
true);
// Read rows (100, infinity) with max_rows = 100. Expect 100 rows with
// more available.
this->ReadRowsAndCheck(kNumColumns, 100, false, -1, max_rows, expected_rows,
true);
// Read rows [950, infinity) with max_rows = 100. Expect 50 rows with
// no more available.
expected_rows = 50;
this->ReadRowsAndCheck(kNumColumns, 950, true, -1, max_rows, expected_rows,
false);
// Read rows (950, infinity) with max_rows = 100 Expect 49 rows with
// no more available.
expected_rows = 49;
this->ReadRowsAndCheck(kNumColumns, 950, false, -1, max_rows, expected_rows,
false);
// Read rows [0, infinity) with max_rows = 10,000. Expect 1,000 rows with
// no more available.
max_rows = 10000;
expected_rows = 1000;
this->ReadRowsAndCheck(kNumColumns, 0, true, -1, max_rows, expected_rows,
false);
// Read rows [0, infinity) with max_rows = 1,000, Expect 1,000 rows with
// no more available.
max_rows = 1000;
expected_rows = 1000;
this->ReadRowsAndCheck(kNumColumns, 0, true, -1, max_rows, expected_rows,
false);
// Read rows [0, infinity) with max_rows = 999, Expect 999 rows with
// more available.
max_rows = 999;
expected_rows = 999;
this->ReadRowsAndCheck(kNumColumns, 0, true, -1, max_rows, expected_rows,
true);
}
TYPED_TEST_P(DataStoreTest, ReadDifferentNumColumns) {
this->set_test_prefix("ReadDifferentNumColumns");
// Add 10 rows of 3 columns each.
this->AddRows(10);
ASSERT_EQ(10u, this->GetNumRows());
// Read rows [3, 6). Expect 3 rows with no more available.
size_t max_rows = UINT32_MAX;
size_t expected_rows = 3;
// Try the read with different numbers of columns specified to read.
for (size_t num_columns = 0; num_columns <= kNumColumns; num_columns++) {
this->ReadRowsAndCheck(num_columns, 3, true, 6, max_rows, expected_rows,
false);
}
// Read row 8 alone.
// Try the read with different numbers of columns specified to read.
for (size_t num_columns = 0; num_columns <= kNumColumns; num_columns++) {
this->ReadSingleRowAndCheck(num_columns, 8, true);
}
}
// Tests deleting ranges of rows.
TYPED_TEST_P(DataStoreTest, DeleteRanges) {
this->set_test_prefix("DeleteRanges");
// Initially there should be no rows.
ASSERT_EQ(0u, this->GetNumRows());
// Add 3000 rows.
this->AddRows(3000);
// Now there should be 3000 rows.
ASSERT_EQ(3000u, this->GetNumRows());
// Delete 10^0 rows starting with row 100.
// i.e. delete row 100
this->DeleteRowsWithPrefix(100, 0);
ASSERT_EQ(2999u, this->GetNumRows());
// Delete 10^1 rows starting with row 200.
// i.e. delete rows [200, 209]
this->DeleteRowsWithPrefix(200, 1);
ASSERT_EQ(2989u, this->GetNumRows());
// Delete 10^2 rows starting with row 300.
// i.e. delete rows [300, 399]
this->DeleteRowsWithPrefix(300, 2);
ASSERT_EQ(2889u, this->GetNumRows());
// Delete 10^3 rows starting with row 0.
// i.e. delete rows [0, 999]
this->DeleteRowsWithPrefix(0, 3);
ASSERT_EQ(2000u, this->GetNumRows());
// Delete 10^3 rows starting with row 1000.
// i.e. delete rows [1000, 1999]
this->DeleteRowsWithPrefix(1000, 3);
ASSERT_EQ(1000u, this->GetNumRows());
// Delete 10^4 rows starting with row 0.
// i.e. delete rows [0, 9999]
this->DeleteRowsWithPrefix(0, 4);
ASSERT_EQ(0u, this->GetNumRows());
}
REGISTER_TYPED_TEST_CASE_P(DataStoreTest, WriteAndReadRows, UnboundedRange,
ReadDifferentNumColumns, DeleteRanges);
} // namespace store
} // namespace analyzer
} // namespace cobalt
#endif // COBALT_ANALYZER_STORE_DATA_STORE_TEST_H_