diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a1868f..92e83ea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,6 @@ name: Tests on: push: - pull_request: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -31,7 +30,7 @@ jobs: matrix: python: ${{ fromJSON(needs.generate-matrix.outputs.python-versions) }} rf-version: ${{ fromJSON(needs.generate-matrix.outputs.rf-versions) }} - name: Windows (${{ matrix.python }}, robotframework-${{ matrix.rf-version }}) + name: Windows (python-${{ matrix.python }}, robotframework-${{ matrix.rf-version }}) steps: - name: Checkout the repository uses: actions/checkout@v4 @@ -51,7 +50,7 @@ jobs: matrix: python: ${{ fromJSON(needs.generate-matrix.outputs.python-versions) }} rf-version: ${{ fromJSON(needs.generate-matrix.outputs.rf-versions) }} - name: Linux (${{ matrix.python }}, robotframework-${{ matrix.rf-version }}) + name: Linux (python-${{ matrix.python }}, robotframework-${{ matrix.rf-version }}) steps: - name: Checkout the repository uses: actions/checkout@v4 @@ -70,7 +69,7 @@ jobs: matrix: python: ${{ fromJSON(needs.generate-matrix.outputs.python-versions) }} rf-version: ${{ fromJSON(needs.generate-matrix.outputs.rf-versions) }} - name: MacOS (${{ matrix.python }}, robotframework-${{ matrix.rf-version }}) + name: MacOS (python-${{ matrix.python }}, robotframework-${{ matrix.rf-version }}) steps: - name: Checkout the repository uses: actions/checkout@v4 diff --git a/src/oxygen/oxygen.py b/src/oxygen/oxygen.py index 825c146..dce6f85 100644 --- a/src/oxygen/oxygen.py +++ b/src/oxygen/oxygen.py @@ -99,7 +99,6 @@ def output_file(self, path): result.visit(OxygenVisitor(self.run_time_data)) result.save() - class OxygenLibrary(OxygenCore): '''Oxygen is a tool to consolidate different test tools' reports together as a single Robot Framework log and report. ``oxygen.OxygenLibrary`` diff --git a/src/oxygen/robot3_interface.py b/src/oxygen/robot3_interface.py index 22b0385..e1a72f5 100644 --- a/src/oxygen/robot3_interface.py +++ b/src/oxygen/robot3_interface.py @@ -288,6 +288,9 @@ def ms_to_timestamp(self, milliseconds): tz_delta = self.get_timezone_delta() time_object = datetime.fromtimestamp(int(milliseconds / 1000)) - tz_delta + if time_object.year < 1970: + time_object = datetime.fromtimestamp(0) + # fromtimestamp() loses milliseconds, add them back milliseconds_delta = timedelta(milliseconds=(milliseconds % 1000)) time_object = (time_object + milliseconds_delta) diff --git a/src/oxygen/robot4_interface.py b/src/oxygen/robot4_interface.py index d1b0cce..e4f4779 100644 --- a/src/oxygen/robot4_interface.py +++ b/src/oxygen/robot4_interface.py @@ -5,8 +5,6 @@ TestCase as RobotResultTest, TestSuite as RobotResultSuite) -from robot.model import BodyItem - from robot.running.model import TestSuite as RobotRunningSuite @@ -227,6 +225,8 @@ def spawn_robot_keyword(self, start_timestamp = self.ms_to_timestamp(start_time) end_timestamp = self.ms_to_timestamp(end_time) + # import this here so RF4 interface stays in parity with RF3 + from robot.model import BodyItem if setup: keyword_type = BodyItem.SETUP elif teardown: @@ -284,11 +284,13 @@ def timestamp_to_ms(self, timestamp): return milliseconds - def ms_to_timestamp(self, milliseconds): tz_delta = self.get_timezone_delta() time_object = datetime.fromtimestamp(int(milliseconds / 1000)) - tz_delta + if time_object.year < 1970: + time_object = datetime.fromtimestamp(0) + # fromtimestamp() loses milliseconds, add them back milliseconds_delta = timedelta(milliseconds=(milliseconds % 1000)) time_object = (time_object + milliseconds_delta) diff --git a/tests/utest/robot_interface/test_time_conversions.py b/tests/utest/robot_interface/test_time_conversions.py index 2d96e3d..4a4498b 100644 --- a/tests/utest/robot_interface/test_time_conversions.py +++ b/tests/utest/robot_interface/test_time_conversions.py @@ -9,25 +9,42 @@ def setUp(self): def test_should_be_correct(self): timestamp = self.interface.result.ms_to_timestamp(1533625284100.0) - assert timestamp == '20180807 07:01:24.100000' + self.assertEqual(timestamp, '20180807 07:01:24.100000') timestamp = self.interface.result.ms_to_timestamp(1533625284451.0) - assert timestamp == '20180807 07:01:24.451000' + self.assertEqual(timestamp, '20180807 07:01:24.451000') def test_should_be_associative(self): timestamp = '20180807 07:01:24.300000' milliseconds = self.interface.result.timestamp_to_ms(timestamp) - assert milliseconds == 1533625284300.0 + self.assertEqual(milliseconds, 1533625284300.0) timestamp = self.interface.result.ms_to_timestamp(milliseconds) - assert timestamp == '20180807 07:01:24.300000' + self.assertEqual(timestamp, '20180807 07:01:24.300000') milliseconds = self.interface.result.timestamp_to_ms(timestamp) - assert milliseconds == 1533625284300.0 + self.assertEqual(milliseconds, 1533625284300.0) timestamp = self.interface.result.ms_to_timestamp(milliseconds) - assert timestamp == '20180807 07:01:24.300000' + self.assertEqual(timestamp, '20180807 07:01:24.300000') + + def _validate_timestamp(self, result): + timestamp = result.ms_to_timestamp(-10) + expected = '19700101 00:00:00.990000' + import platform + # Particular Windows 10 calculates epoch differently ( T ʖ̯ T) + if platform.system() == 'Windows' and platform.version() == '10.0.19044': + expected = '19700101 02:00:00.990000' + + self.assertEqual(timestamp, expected) + + def test_ms_before_epoch_are_reset_to_epoch(self): + from oxygen.robot4_interface import RobotResultInterface as RF4ResultIface + self._validate_timestamp(RF4ResultIface()) + + from oxygen.robot3_interface import RobotResultInterface as RF3ResultIface + self._validate_timestamp(RF3ResultIface()) class TestTimestampToMs(TestCase): @@ -36,19 +53,19 @@ def setUp(self): def test_should_be_correct(self): milliseconds = self.iface.result.timestamp_to_ms('20180807 07:01:24.000') - assert milliseconds == 1533625284000.0 + self.assertEqual(milliseconds, 1533625284000.0) milliseconds = self.iface.result.timestamp_to_ms('20180807 07:01:24.555') - assert milliseconds == 1533625284555.0 + self.assertEqual(milliseconds, 1533625284555.0) def test_should_be_associative(self): milliseconds = 1533625284300.0 timestamp = self.iface.result.ms_to_timestamp(milliseconds) - assert timestamp == '20180807 07:01:24.300000' + self.assertEqual(timestamp, '20180807 07:01:24.300000') milliseconds = self.iface.result.timestamp_to_ms(timestamp) - assert milliseconds == 1533625284300.0 + self.assertEqual(milliseconds, 1533625284300.0) timestamp = self.iface.result.ms_to_timestamp(milliseconds) - assert timestamp == '20180807 07:01:24.300000' + self.assertEqual(timestamp, '20180807 07:01:24.300000')