<markdown> # Test Harness

The TestHarness class in NebulaStream is a framework to run quey deployment tests. This page shows how to create a test using the TestHarness class.

## Creating a Query Deployment Test Using The TestHarness In this example, we will go through the process of creating a test to deploy a simple query with a filter operator.

#### Prepare a test case.

The test case is similar to other tests. ``` TEST_F(Example, exampleTest) {


} ``` #### Instantiate a TestHarness Class

TestHarness takes a query (without the sink operator), a restPort, and a rpcPort as its constructor parameter. ``` TEST_F(Example, exampleTest) {

// instantiate a TestHarness object
std::string queryWithFilterOperator = 
			R"(Query::from("car").filter(Attribute("key") < 30))";
  TestHarness testHarness = TestHarness(queryWithFilterOperator, restPort, rpcPort);

} ```

#### Define and Add Sources

TestHarness supports two kinds of sources: MemorySource and CSVSource. To add a memory source, we need to prepare the struct defining a Tuple of the source and the schema of the source. After that, we can call `addMemorySource` and pass three parameters: the name of the logical source, the schema, and the name of the physical source. ``` TEST_F(Example, exampleTest) {

  // define and add memory sources
  struct Car { uint32_t key; uint32_t value;uint64_t timestamp;};
  auto carSchema = Schema::create()->addField("key", DataTypeFactory::createUInt32())
          ->addField("value", DataTypeFactory::createUInt32())->addField("timestamp", 
  testHarness.addMemorySource("car", carSchema, "car_physical_stream_1");
  testHarness.addMemorySource("car", carSchema, "car_physical_stream_2");

} ``` To add a memory source, we need to first define a `PhysicalSourceConfigPtr`. After that, we call `addCSVSource` and pass this object along with the schema used in the source to be added. ``` TEST_F(Example, exampleTest) {

  // define and add csv sources
  PhysicalSourceConfigPtr conf = PhysicalSourceConfig::create(sourceConfig);
  testHarness.addCSVSource(conf, carSchema);

} ``` Note:

- `sizeOf(TupleStruct) == schema.getSizeInBytes()` (The size of the Struct must equal to the size of the schema in bytes) - `bufferSize* % sizeOf(TupleStruct) == 0` (The default value of bufferSize is `4096`. The `bufferSize` should be divisible by the size of the struct. Currently, we do not support a tuple that spans multiple buffers.)

#### Pushing Element to The Memory Source (not applicable to CSVSource)

To push a tuple to a memory source, we can call the `pushElement()` from the TestHarness object. This method takes an instance of the struct of the tuple and the index of the source as parameters. ``` TEST_F(Example, exampleTest) {

  // push elements to sources
  testHarness.pushElement<Car>({40, 40, 40}, 0);
  testHarness.pushElement<Car>({30, 30, 30}, 1);
  testHarness.pushElement<Car>({71, 71, 71}, 1);
  testHarness.pushElement<Car>({21, 21, 21}, 0);

``` Note: - All pushed elements/tuples will be stored in a single buffer when it is not full. (`sizeOf(Tuple) * nTuples < 4096`)

#### Defining the Expected Output

We need to define a struct of the expected output. The struct needs to overload the `==` operator to compare between two instances. We then create a vector of this struct to define what output we expect from the test. ``` TEST_F(Example, exampleTest) {

  // define the expected output
  struct Output {uint32_t key; uint32_t value;uint64_t timestamp;
  bool operator==(Output const& rhs) const { return (key == rhs.key 
                      && value == rhs.value && timestamp == rhs.timestamp); }
  std::vector<Output> expectedOutput = {{40, 40, 40}, 
                                        {71, 71, 71}};

} ``` Notes: - The output data types have to match the input data type. - Output struct should not lead to paddings.

#### Comparing the Actual Output and the Expected Output

In this final step, we first obtain the actual output from the TestHarness by calling `getOutput()`. This method will execute the query deployment and return the output of the query. We can then use `EXPECT_EQ` and `EXPECT_THAT` to compare the actual output to the expected output. ``` TEST_F(Example, exampleTest) {

  // compare the actual output to the expected output
  std::vector<Output> actualOutput = testHarness.getOutput<Output>(expectedOutput.size());
  EXPECT_EQ(actualOutput.size(), expectedOutput.size());
  EXPECT_THAT(actualOutput, ::testing::UnorderedElementsAreArray(expectedOutput));

} ```

### Creating Hierarchical Topology

In the previous example, we only create a flat topology with `n` workers. The TestHarness framework allows us to create a hierarchical framework by specifying the parent worker. Furthermore, we can also add non-source worker (e.g., the workers between sensors and the sink) using the `addNonSourceWorker` method. ``` TEST_F(Example, exampleTest) { … add hierarchical topology testHarness.addNonSourceWorker(); worker with idx=0 (with the sink node as default parent)

  testHarness.addNonSourceWorker(testHarness.getWorkerId(0)); //worker with idx=1
  testHarness.addMemorySource("car", carSchema, "car1", 
                          testHarness.getWorkerId(1)); //worker with idx = 2

… } ```

### More Example

More examples of test cases using the TestHarness framework can be found in `/test/Integration/TestHarnessUtilTest`. </markdow n>

test_harness.txt · Last modified: 2022/03/04 17:36 by
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki