From ebdd5b6a2119c9fc1d59e0a03b5526bc1e9b4a69 Mon Sep 17 00:00:00 2001
From: Nikolay Stanchev <ns17@it-innovation.soton.ac.uk>
Date: Tue, 5 Jun 2018 10:38:25 +0100
Subject: [PATCH] Added extensive comments to the aggregator's unit test

---
 .../aggregation/test_aggregator.py            | 67 +++++++++++++++----
 1 file changed, 55 insertions(+), 12 deletions(-)

diff --git a/src/service/clmcservice/aggregation/test_aggregator.py b/src/service/clmcservice/aggregation/test_aggregator.py
index 14a9e94..ef22fb4 100644
--- a/src/service/clmcservice/aggregation/test_aggregator.py
+++ b/src/service/clmcservice/aggregation/test_aggregator.py
@@ -21,21 +21,34 @@
 ##      Created Date :          04-06-2018
 ##      Created for Project :   FLAME
 """
-from collections import OrderedDict
+
 from threading import Event
 from unittest import mock
-from time import sleep
 from clmcservice.aggregation.aggregator import AggregatorThread
 from clmcservice.aggregation.influx_data_interface import MockResultSet, network_result_item, service_result_item, drop_timestamp, generate_e2e_no_timestamp_row
 
 
 class TestAggregation(object):
+    """
+    A unit test to ensure the functionality of the aggregator is correct.
+    """
 
-    ACTUAL_RESULTS = "actual_aggregated_results"
-    EXPECTED_RESULTS = "expected_aggregated_results"
-    FINISHED = "finished_event"
+    ACTUAL_RESULTS = "actual_aggregated_results"  # the attribute name of the actual results data structure
+    EXPECTED_RESULTS = "expected_aggregated_results"  # the attribute name of the expected results data structure
+    FINISHED = "finished_event"  # the attribute name of the flag object, which marks the end of the test
 
     def points_generator(self, network_items, service_items):
+        """
+        A generator method intended to be used by the mock db client when involving the mocked query() method. It takes the network and service items, and generates a result from
+        those items each time query() is called by taking turns - starts with network result, followed by service result and then it repeats, until all items have been exhausted.
+        Network items and service items are expected to have the same length.
+
+        :param network_items: the network data to generate from
+        :param service_items: the service data to generate from
+
+        :return: a generator object
+        """
+
         assert len(network_items) == len(service_items), "The data points generator must receive the same number of network items as the number of service items"
         index = 0
 
@@ -47,6 +60,7 @@ class TestAggregation(object):
 
             # before yielding the service data points, check if both sets of data points are enumerated
             if index == len(network_items)-1:
+                # if so, set the finished flag of the test
                 getattr(self, self.FINISHED).set()
 
             yield MockResultSet(items)
@@ -54,7 +68,14 @@ class TestAggregation(object):
             index += 1
 
     def setup_mock_db_client(self, mock_class):
-        setattr(self, self.ACTUAL_RESULTS, [])
+        """
+        Sets up a mock db client and also defines the expected aggregation results from the test.
+
+        :param mock_class: the mock class used as an influx db client instance
+        :return:
+        """
+
+        setattr(self, self.ACTUAL_RESULTS, [])  # initially, there are no actual results, these are built progressively while the aggregator is running
         setattr(self, self.EXPECTED_RESULTS, [
             generate_e2e_no_timestamp_row(path_id="SR1-SR3", source_sfr="SR1", target_sfr="SR3", endpoint="endpoint1", sf_instance="ms1.flame.org", delay_forward=10,
                                           delay_reverse=15, delay_service=10, avg_request_size=1024, avg_response_size=8, avg_bandwidth=1200),
@@ -74,10 +95,13 @@ class TestAggregation(object):
                                           delay_reverse=27, delay_service=105, avg_request_size=1024, avg_response_size=128, avg_bandwidth=1200),
             generate_e2e_no_timestamp_row(path_id="SR14-SR15", source_sfr="SR15", target_sfr="SR14", endpoint="endpoint14", sf_instance="ms1.flame.org", delay_forward=27,
                                           delay_reverse=24, delay_service=85, avg_request_size=32, avg_response_size=64, avg_bandwidth=1200),
-        ])
+        ])  # defines the expected rows from the aggregation
         setattr(self, self.FINISHED, Event())
 
+        # initialises the influx data generator, which is involved each time the query() method of the mock db client is called
         mock_points = self.points_generator(
+            # network items is a list of tuples, each tuple represents a result from a query; each time query() is called and a network measurement must be generated, then one of
+            # these tuples is generated, empty tuple means result with no points
             network_items=[
                 (
                     network_result_item("network_delays", "SR1-SR3", "SR1", "SR3", 10, 1200),
@@ -115,6 +139,8 @@ class TestAggregation(object):
                     network_result_item("network_delays", "SR14-SR15", "SR15", "SR14", 27, 1200),
                 )
             ],
+            # service items is a list of tuples, each tuple represents a result from a query; each time query() is called and a service measurement must be generated, then one of
+            # these tuples is generated, empty tuple means result with no points
             service_items=[
                 (
                     service_result_item("service_delays", "SR3", "endpoint1", "ms1.flame.org", 10, 1024, 8),
@@ -144,25 +170,42 @@ class TestAggregation(object):
             ]
         )
 
-        mock_class.query = lambda query: next(mock_points)
-        mock_class.write_points = lambda points: getattr(self, self.ACTUAL_RESULTS).append(drop_timestamp(points[0]))
+        # implement the query() and write_points() methods of the mock db client
+        mock_class.query = lambda query: next(mock_points)  # query() returns the next element of the mock_points generator
+        mock_class.write_points = lambda points: getattr(self, self.ACTUAL_RESULTS).append(drop_timestamp(points[0]))  # write_points() adds aggregated rows to actual results list
+
+        # in the end of the test, we can compare the expected results with the actual results that were generated during the aggregation process
 
     @mock.patch('clmcservice.aggregation.aggregator.InfluxDBClient', autospec=True)
     def test_aggregator(self, MockDBClient):
+        """
+        The actual test that's executed when running pytest.
+
+        :param MockDBClient: a mock object argument passed by the mock.patch decorator. The decorator changes all occurrences of InfluxDBClient in the aggregator's code to the
+        return value of this MockDBClient object
+        """
+
+        # set up the mock db client by providing implementations for the necessary methods (query and write_points)
         self.setup_mock_db_client(MockDBClient.return_value)
 
+        # start the aggregator as a thread, report period set to 2 so that the unit tests is not taking too long
         t = AggregatorThread(report_period=2)
         t.start()
 
-        while not getattr(self, self.FINISHED).is_set():
-            sleep(1)
+        # wait until the finished flag has been set
+        getattr(self, self.FINISHED).wait()
 
+        # stop the thread when the aggregation has finished
         t.stop()
 
+        # compare the expected results with teh actual results that were collected during the aggregation process
         expected_results = getattr(self, self.EXPECTED_RESULTS)
         actual_results = getattr(self, self.ACTUAL_RESULTS)
         assert type(actual_results) is list
         assert type(expected_results) is list
         assert len(actual_results) == len(expected_results), "Actual and expected result differ in length."
-        assert sorted(actual_results, key=lambda k: k['tags']['path_ID'])  == sorted(expected_results, key=lambda k: k['tags']['path_ID']), \
+
+        # we compare sorted versions of the expected and actual results; this is because the aggregator implementation uses dictionary for efficiency purposes, hence the order of
+        # the collected results may vary, especially on different OS; hence, we only care about the two list of results to contain the same elements
+        assert sorted(actual_results, key=lambda k: k['tags']['path_ID']) == sorted(expected_results, key=lambda k: k['tags']['path_ID']), \
             "Test failure - aggregation process returns incorrect results."
-- 
GitLab