diff --git a/docs/developers-setup.md b/docs/developers-setup.md index 6a539b727..a31842a89 100644 --- a/docs/developers-setup.md +++ b/docs/developers-setup.md @@ -43,6 +43,12 @@ 6. Start running the extension locally by pressing F5 or going to VS Code Debug menu and select 'Start debugging' +## Testing + +- To run unit tests, run the command: `npm run test` in the root level directory. + - You will need Pytest installed for the Python tests to run correctly +- To run just the python tests, run the command: `pytest src` or `python -m pytest src` in the root level directory. + ## Notes on how to use it - [Documentation to use the Extension](/docs/how-to-use.md) diff --git a/src/adafruit_circuitplayground/debugger_communication_client.py b/src/adafruit_circuitplayground/debugger_communication_client.py index 32f02d273..0536550ea 100644 --- a/src/adafruit_circuitplayground/debugger_communication_client.py +++ b/src/adafruit_circuitplayground/debugger_communication_client.py @@ -47,5 +47,5 @@ def button_press(data): # Event : Sensor changed (Temperature, light, Motion) @sio.on("sensor_changed") -def button_press(data): +def sensor_changed(data): __update_api_state(data, CONSTANTS.EVENTS_SENSOR_CHANGED) diff --git a/src/adafruit_circuitplayground/express.py b/src/adafruit_circuitplayground/express.py index 99507a691..f08e1b65c 100644 --- a/src/adafruit_circuitplayground/express.py +++ b/src/adafruit_circuitplayground/express.py @@ -4,7 +4,7 @@ import json import sys import os -from playsound import playsound +import playsound from .pixel import Pixel from . import utils from . import constants as CONSTANTS @@ -162,7 +162,7 @@ def play_file(self, file_name): if sys.implementation.version[0] >= 3: if file_name.endswith(".wav"): try: - playsound(abs_path_wav_file) + playsound.playsound(abs_path_wav_file) except: # TODO TASK: 29054 Verfication of a "valid" .wav file raise EnvironmentError(CONSTANTS.NOT_SUITABLE_FILE_ERROR) diff --git a/src/adafruit_circuitplayground/test/test_debugger_communication_client.py b/src/adafruit_circuitplayground/test/test_debugger_communication_client.py new file mode 100644 index 000000000..b25e27eb8 --- /dev/null +++ b/src/adafruit_circuitplayground/test/test_debugger_communication_client.py @@ -0,0 +1,63 @@ +import pytest +import json # Remove +from unittest import mock +import socketio + +from .. import express +from .. import debugger_communication_client + + +class TestDebuggerCommunicationClient(object): + @mock.patch("socketio.Client.connect") + def test_init_connection(self, mock_connect): + mock_connect.return_value = None + debugger_communication_client.init_connection() + mock_connect.assert_called_once() + + def test_init_connection1(self): + socketio.Client.connect = mock.Mock() + socketio.Client.connect.return_value = None + debugger_communication_client.init_connection() + socketio.Client.connect.assert_called_once() + + def test_update_state(self): + socketio.Client.emit = mock.Mock() + socketio.Client.emit.return_value = None + debugger_communication_client.update_state({}) + socketio.Client.emit.assert_called_once() + + @mock.patch.dict( + express.cpx._Express__state, + {"button_a": False, "button_b": False, "switch": True}, + clear=True, + ) + def test_button_press(self): + data = {"button_a": True, "button_b": True, "switch": True} + serialized_data = json.dumps(data) + debugger_communication_client.button_press(serialized_data) + assert data == express.cpx._Express__state + + @mock.patch.dict( + express.cpx._Express__state, + {"temperature": 0, "light": 0, "motion_x": 0, "motion_y": 0, "motion_z": 0}, + clear=True, + ) + def test_sensor_changed(self): + data = { + "temperature": 1, + "light": 2, + "motion_x": 3, + "motion_y": 4, + "motion_z": 5, + } + serialized_data = json.dumps(data) + debugger_communication_client.sensor_changed(serialized_data) + assert data == express.cpx._Express__state + + @mock.patch("builtins.print") + @mock.patch.dict(express.cpx._Express__state, {}, clear=True) + def test_update_api_state_fail(self, mocked_print): + data = [] + debugger_communication_client.sensor_changed(data) + # Exception is caught and a print is stated to stderr + mocked_print.assert_called_once() diff --git a/src/adafruit_circuitplayground/test/test_express.py b/src/adafruit_circuitplayground/test/test_express.py index 07d96acdd..b83ba3a23 100644 --- a/src/adafruit_circuitplayground/test/test_express.py +++ b/src/adafruit_circuitplayground/test/test_express.py @@ -1,5 +1,7 @@ import pytest +from unittest import mock +import playsound from ..express import Express from ..pixel import Pixel @@ -18,105 +20,96 @@ def setup_method(self): self.pixels = Pixel(self.__state) self.__speaker_enabled = False + def test_acceleration(self): + mock_motion_x = 10 + mock_motion_y = -10 + mock_motion_z = -20 + self.cpx._Express__state["motion_x"] = mock_motion_x + self.cpx._Express__state["motion_y"] = mock_motion_y + self.cpx._Express__state["motion_z"] = mock_motion_z + accel = self.cpx.acceleration + assert accel[0] == 10 + assert accel[1] == -10 + assert accel[2] == -20 + def test_button_a(self): self.cpx._Express__state["button_a"] = True - assert True == self.cpx.button_a + assert self.cpx.button_a def test_button_b(self): self.cpx._Express__state["button_b"] = True - assert True == self.cpx.button_b + assert self.cpx.button_b + + def test_taps(self): + self.cpx._Express__state["detect_taps"] = 2 + assert 2 == self.cpx.detect_taps + + @pytest.mark.parametrize("taps, expected", [(1, 1), (2, 2), (3, 1)]) + def test_taps_setter(self, taps, expected): + self.cpx.detect_taps = taps + assert expected == self.cpx.detect_taps def test_red_led(self): self.cpx._Express__state["red_led"] = True - assert True == self.cpx.red_led + assert self.cpx.red_led def test_red_led_int(self): self.cpx.red_led = 3 - assert True == self.cpx.red_led + assert self.cpx.red_led def test_red_led_string(self): self.cpx.red_led = "foo" - assert True == self.cpx.red_led + assert self.cpx.red_led def test_switch(self): self.cpx._Express__state["switch"] = True - assert True == self.cpx.switch - - def test_set_item_tuple(self): - self.cpx.pixels[0] = (255, 255, 255) - assert (255, 255, 255) == self.cpx._Express__state["pixels"][0] + assert self.cpx.switch - def test_set_item_list(self): - self.cpx.pixels[0] = [255, 255, 255] - assert (255, 255, 255) == self.cpx._Express__state["pixels"][0] + def test_temperature(self): + self.cpx._Express__state["temperature"] = 31 + assert 31 == self.cpx.temperature - def test_set_item_hex(self): - self.cpx.pixels[0] = 0xFFFFFF - assert (255, 255, 255) == self.cpx._Express__state["pixels"][0] - - def test_set_item_invalid(self): - with pytest.raises(ValueError): - self.cpx.pixels[0] = "hello" + def test_light(self): + self.cpx._Express__state["light"] = 255 + assert 255 == self.cpx.light def test_shake(self): self.cpx._Express__state["shake"] = True - assert True == self.cpx.shake() + assert self.cpx.shake() def test_touch_A1(self): self.cpx._Express__state["touch"][0] = True - assert True == self.cpx.touch_A1 + assert self.cpx.touch_A1 def test_touch_A2(self): self.cpx._Express__state["touch"][1] = True - assert True == self.cpx.touch_A2 + assert self.cpx.touch_A2 def test_touch_A3(self): self.cpx._Express__state["touch"][2] = True - assert True == self.cpx.touch_A3 + assert self.cpx.touch_A3 def test_touch_A4(self): self.cpx._Express__state["touch"][3] = True - assert True == self.cpx.touch_A4 + assert self.cpx.touch_A4 def test_touch_A5(self): self.cpx._Express__state["touch"][4] = True - assert True == self.cpx.touch_A5 + assert self.cpx.touch_A5 def test_touch_A6(self): self.cpx._Express__state["touch"][5] = True - assert True == self.cpx.touch_A6 + assert self.cpx.touch_A6 def test_touch_A7(self): self.cpx._Express__state["touch"][6] = True - assert True == self.cpx.touch_A7 + assert self.cpx.touch_A7 - def test_play_file_mp4(self): + def test_play_file_mp4_wrong_type(self): with pytest.raises(TypeError): self.cpx.play_file("sample.mp4") - def test_fill(self): - self.cpx.pixels.fill((0, 255, 0)) - expected_pixels = [(0, 255, 0)] * 10 - assert expected_pixels == self.cpx._Express__state["pixels"] - - def test_extract_pixel_value_list(self): - assert (0, 255, 0) == self.cpx.pixels._Pixel__extract_pixel_value((0, 255, 0)) - - def test_extract_pixel_value_list1(self): - assert (123, 123, 123) == self.cpx.pixels._Pixel__extract_pixel_value( - [123, 123, 123] - ) - - def test_extract_pixel_value_int(self): - assert (0, 0, 255) == self.cpx.pixels._Pixel__extract_pixel_value(255) - - def test_extract_pixel_value_tuple(self): - assert (0, 255, 0) == self.cpx.pixels._Pixel__extract_pixel_value((0, 255, 0)) - - def test_extract_pixel_value_invalid_length(self): - with pytest.raises(ValueError): - self.cpx.pixels._Pixel__extract_pixel_value((1, 2, 3, 4)) - - def test_extract_pixel_value_invalid_tuple_value(self): - with pytest.raises(ValueError): - self.cpx.pixels._Pixel__extract_pixel_value((0, 222, "hello")) + def test_play_file_mp4(self): + playsound.playsound = mock.Mock() + self.cpx.play_file("sample.wav") + playsound.playsound.assert_called_once() diff --git a/src/adafruit_circuitplayground/test/test_pixel.py b/src/adafruit_circuitplayground/test/test_pixel.py new file mode 100644 index 000000000..2532611db --- /dev/null +++ b/src/adafruit_circuitplayground/test/test_pixel.py @@ -0,0 +1,120 @@ +import pytest + +from ..pixel import Pixel + + +class TestPixel(object): + def setup_method(self): + self.pixel = Pixel( + { + "brightness": 1.0, + "button_a": False, + "button_b": False, + "pixels": [(255, 0, 0), (0, 255, 0), (0, 0, 255)], + "red_led": False, + "switch": False, + } + ) + + @pytest.mark.parametrize("debug_mode", [True, False]) + def test_set_debug_mode(self, debug_mode): + self.pixel._Pixel__set_debug_mode(debug_mode) + assert debug_mode == self.pixel._Pixel__debug_mode + + def test_get_item_out_of_bounds(self): + with pytest.raises(IndexError): + p = self.pixel[3] + + def test_get_item(self): + assert (0, 0, 255) == self.pixel[2] + + def test_get_item_splice(self): + assert [(255, 0, 0), (0, 255, 0)] == self.pixel[0:2] + + def test_set_item(self): + self.pixel[1] = (50, 50, 50) + assert (50, 50, 50) == self.pixel[1] + + def test_set_item_splice(self): + self.pixel[0:1] = [(100, 100, 100), (0, 0, 100)] + assert (100, 100, 100) == self.pixel[0] + assert (0, 0, 100) == self.pixel[1] + + def test_set_item_out_of_bounds(self): + with pytest.raises(IndexError): + self.pixel[3] = (0, 0, 0) + + def test_set_item_invalid(self): + with pytest.raises(ValueError): + self.pixel[0] = "hello" + + def test_len(self): + assert 3 == len(self.pixel) + + @pytest.mark.parametrize("index, expected", [(0, True), (3, False)]) + def test_valid_index(self, index, expected): + assert self.pixel._Pixel__valid_index(index) == expected + + def test_fill(self): + self.pixel.fill((123, 123, 123)) + assert all(p == (123, 123, 123) for p in self.pixel._Pixel__state["pixels"]) + + @pytest.mark.parametrize( + "val, expected", + [([3, 4, 5], (3, 4, 5)), (432, (0, 1, 176)), ((1, 2, 3), (1, 2, 3))], + ) + def test_extract_pixel_values_not_slice(self, val, expected): + assert expected == self.pixel._Pixel__extract_pixel_value(val) + + @pytest.mark.parametrize( + "val, expected", + [ + ([[3, 4, 5], [6, 7, 8]], [(3, 4, 5), (6, 7, 8)]), + ([444555, 666777], [(6, 200, 139), (10, 44, 153)]), + ([(10, 10, 10), (20, 20, 20)], [(10, 10, 10), (20, 20, 20)]), + ], + ) + def test_extract_pixel_values_slice(self, val, expected): + assert expected == self.pixel._Pixel__extract_pixel_value(val, is_slice=True) + + @pytest.mark.parametrize("val", [[1, 2, 3, 4], [1, 2], 0.3]) + def test_extract_pixel_values_fail(self, val): + with pytest.raises(ValueError): + self.pixel._Pixel__extract_pixel_value(val) + + def test_hex_to_rgb_fail(self): + with pytest.raises(ValueError): + self.pixel._Pixel__hex_to_rgb("x") + + @pytest.mark.parametrize( + "hex, expected", + [ + ("0xffffff", (255, 255, 255)), + ("0x0", (0, 0, 0)), + ("0xff0000", (255, 0, 0)), + ("0xabcdef", (171, 205, 239)), + ], + ) + def test_hex_to_rgb(self, hex, expected): + assert expected == self.pixel._Pixel__hex_to_rgb(hex) + + @pytest.mark.parametrize( + "pixValue, expected", + [(0, True), (200, True), (255, True), (-1, False), (256, False), ("1", False)], + ) + def test_valid_rgb_value(self, pixValue, expected): + assert expected == self.pixel._Pixel__valid_rgb_value(pixValue) + + def test_get_brightness(self): + self.pixel._Pixel__state["brightness"] = 0.4 + assert 0.4 == pytest.approx(self.pixel.brightness) + + @pytest.mark.parametrize("brightness", [-0.1, 1.1]) + def test_set_brightness_fail(self, brightness): + with pytest.raises(ValueError): + self.pixel.brightness = brightness + + @pytest.mark.parametrize("brightness", [0, 1, 0.4, 0.333]) + def test_set_brightness(self, brightness): + self.pixel.brightness = brightness + assert self.pixel._Pixel__valid_brightness(brightness) diff --git a/src/adafruit_circuitplayground/test/test_utils.py b/src/adafruit_circuitplayground/test/test_utils.py new file mode 100644 index 000000000..3c25128e6 --- /dev/null +++ b/src/adafruit_circuitplayground/test/test_utils.py @@ -0,0 +1,28 @@ +import sys + +from unittest import mock + +from .. import constants as CONSTANTS +from .. import utils + + +class TestUtils(object): + def test_remove_leading_slashes(self): + original = "///a//b/" + expected = "a//b/" + assert expected == utils.remove_leading_slashes(original) + + def test_escape_notOSX(self): + if sys.platform.startswith(CONSTANTS.MAC_OS): + utils.sys = mock.MagicMock() + utils.sys.configure_mock(platform="win32") + original = "a b" + assert original == utils.escape_if_OSX(original) + + def test_escape_isOSX(self): + if not sys.platform.startswith(CONSTANTS.MAC_OS): + utils.sys = mock.MagicMock() + utils.sys.configure_mock(platform="darwin") + original = "a b" + expected = "a%20b" + assert expected == utils.escape_if_OSX(original) diff --git a/src/test_code/code.py b/src/test_code/code.py deleted file mode 100644 index abeaff3b1..000000000 --- a/src/test_code/code.py +++ /dev/null @@ -1,18 +0,0 @@ -from adafruit_circuitplayground.express import cpx -import time - -cpx.pixels.brightness = 0.3 -cpx.pixels.fill((0, 0, 0)) # Turn off the NeoPixels if they're on! -cpx.pixels.show() - -while True: - if cpx.button_a: - cpx.pixels[2] = (0, 255, 0) - else: - cpx.pixels[2] = (0, 0, 0) - - if cpx.button_b: - cpx.pixels[7] = (0, 0, 255) - else: - cpx.pixels[7] = (0, 0, 0) - cpx.pixels.show() diff --git a/src/test_code/control.py b/src/test_code/control.py deleted file mode 100644 index ad8f6c0e5..000000000 --- a/src/test_code/control.py +++ /dev/null @@ -1,38 +0,0 @@ -import sys -import json - -# Read data from stdin - - -def read_in(): - lines = sys.stdin.readlines() - # Since our input would only be having one line, parse our JSON data from that - return json.loads(lines[0]) - - -def main(): - # get our data as an array from read_in() - # lines = read_in() - - openCmd = { - "pixels": [ - (0, 0, 255), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - ], - "button_a": False, - "button_b": False, - } - print(json.dumps(openCmd)) - - -# start process -if __name__ == "__main__": - main()