def test_Module_init_directory_module(): """ Ensure the Module instance is set up as expected and logged, as if for a directory based Python module. """ path = os.path.join("foo", "bar", "dir_module", "") repo = "https://github.com/adafruit/SomeLibrary.git" device_version = "1.2.3" bundle_version = "3.2.1" mpy = True with mock.patch("circup.logger.info") as mock_logger, mock.patch( "circup.os.path.isfile", return_value=False ), mock.patch("circup.CPY_VERSION", "4.1.2"), mock.patch( "circup.DATA_DIR", "/tests/DATA_DIR" ), mock.patch( "circup.Bundle.lib_dir", return_value="tests" ): bundle = circup.Bundle(TEST_BUNDLE_NAME) m = circup.Module( path, repo, device_version, bundle_version, mpy, bundle, (None, None) ) mock_logger.assert_called_once_with(m) assert m.path == path assert m.file is None assert m.name == "dir_module" assert m.repo == repo assert m.device_version == device_version assert m.bundle_version == bundle_version assert m.bundle_path == os.path.join("tests", m.name) assert m.mpy is True
def test_get_bundle_network_error(): """ Ensure that if there is a network related error when grabbing the bundle then the error is logged and re-raised for the HTTP status code. """ with mock.patch("circup.requests") as mock_requests, mock.patch( "circup.tags_data_load", return_value=dict() ), mock.patch("circup.logger") as mock_logger: # Force failure with != requests.codes.ok mock_requests.get().status_code = mock_requests.codes.BANG # Ensure raise_for_status actually raises an exception. mock_requests.get().raise_for_status.return_value = Exception("Bang!") mock_requests.get.reset_mock() tag = "12345" with pytest.raises(Exception) as ex: bundle = circup.Bundle(TEST_BUNDLE_NAME) circup.get_bundle(bundle, tag) assert ex.value.args[0] == "Bang!" url = ( "https://github.com/" + TEST_BUNDLE_NAME + "/releases/download" "/{tag}/adafruit-circuitpython-bundle-py-{tag}.zip".format(tag=tag) ) mock_requests.get.assert_called_once_with(url, stream=True) assert mock_logger.warning.call_count == 1 mock_requests.get().raise_for_status.assert_called_once_with()
def test_find_modules(): """ Ensure that the expected list of Module instances is returned given the metadata dictionary fixtures for device and bundle modules. """ with open("tests/device.json") as f: device_modules = json.load(f) with open("tests/bundle.json") as f: bundle_modules = json.load(f) with mock.patch( "circup.get_device_versions", return_value=device_modules ), mock.patch( "circup.get_bundle_versions", return_value=bundle_modules ), mock.patch( "circup.os.path.isfile", return_value=True ): bundle = circup.Bundle(TEST_BUNDLE_NAME) bundles_list = [bundle] for module in bundle_modules: bundle_modules[module]["bundle"] = bundle result = circup.find_modules("", bundles_list) assert len(result) == 1 assert result[0].name == "adafruit_74hc595" assert ( result[0].repo == "https://github.com/adafruit/Adafruit_CircuitPython_74HC595.git" )
def test_Module_repr(): """ Ensure the repr(dict) is returned (helps when logging). """ path = os.path.join("foo", "bar", "baz", "local_module.py") repo = "https://github.com/adafruit/SomeLibrary.git" device_version = "1.2.3" bundle_version = "3.2.1" with mock.patch("circup.os.path.isfile", return_value=True), mock.patch( "circup.CPY_VERSION", "4.1.2" ), mock.patch("circup.Bundle.lib_dir", return_value="tests"): bundle = circup.Bundle(TEST_BUNDLE_NAME) m = circup.Module( path, repo, device_version, bundle_version, False, bundle, (None, None) ) assert repr(m) == repr( { "path": path, "file": "local_module.py", "name": "local_module", "repo": repo, "device_version": device_version, "bundle_version": bundle_version, "bundle_path": os.path.join("tests", m.file), "mpy": False, "min_version": None, "max_version": None, } )
def test_get_bundle(): """ Ensure the expected calls are made to get the referenced bundle and the result is unzipped to the expected location. """ # All these mocks stop IO side effects and allow us to spy on the code to # ensure the expected calls are made with the correct values. Warning! Here # Be Dragons! (If in doubt, ask ntoll for details). mock_progress = mock.MagicMock() mock_progress().__enter__ = mock.MagicMock(return_value=["a", "b", "c"]) mock_progress().__exit__ = mock.MagicMock() with mock.patch("circup.requests") as mock_requests, mock.patch( "circup.click" ) as mock_click, mock.patch( "circup.open", mock.mock_open() ) as mock_open, mock.patch( "circup.os.path.isdir", return_value=True ), mock.patch( "circup.shutil" ) as mock_shutil, mock.patch( "circup.zipfile" ) as mock_zipfile: mock_click.progressbar = mock_progress mock_requests.get().status_code = mock_requests.codes.ok mock_requests.get.reset_mock() tag = "12345" bundle = circup.Bundle(TEST_BUNDLE_NAME) circup.get_bundle(bundle, tag) assert mock_requests.get.call_count == 3 assert mock_open.call_count == 3 assert mock_shutil.rmtree.call_count == 3 assert mock_zipfile.ZipFile.call_count == 3 assert mock_zipfile.ZipFile().__enter__().extractall.call_count == 3
def test_Module_mpy_mismatch(): """ Ensure the ``outofdate`` property on a Module instance returns the expected boolean value to correctly indicate if the referenced module is, in fact, out of date. """ path = os.path.join("foo", "bar", "baz", "module.mpy") repo = "https://github.com/adafruit/SomeLibrary.git" with mock.patch("circup.CPY_VERSION", "6.1.2"): bundle = circup.Bundle(TEST_BUNDLE_NAME) m1 = circup.Module(path, repo, "1.2.3", "1.2.3", True, bundle, (None, None)) m2 = circup.Module( path, repo, "1.2.3", "1.2.3", True, bundle, ("7.0.0-alpha.1", None) ) m3 = circup.Module( path, repo, "1.2.3", "1.2.3", True, bundle, (None, "7.0.0-alpha.1") ) with mock.patch("circup.CPY_VERSION", "6.2.0"): assert m1.mpy_mismatch is False assert m1.outofdate is False assert m2.mpy_mismatch is True assert m2.outofdate is True assert m3.mpy_mismatch is False assert m3.outofdate is False with mock.patch("circup.CPY_VERSION", "7.0.0"): assert m1.mpy_mismatch is False assert m1.outofdate is False assert m2.mpy_mismatch is False assert m2.outofdate is False assert m3.mpy_mismatch is True assert m3.outofdate is True
def test_Bundle_lib_dir(): """ Check the return of Bundle.lib_dir with a test tag. """ bundle_data = {TEST_BUNDLE_NAME: "TESTTAG"} with mock.patch("circup.logger.info"), mock.patch( "circup.os.path.isfile", return_value=True ), mock.patch("circup.tags_data_load", return_value=bundle_data), mock.patch( "circup.DATA_DIR", "DATA_DIR" ): bundle = circup.Bundle(TEST_BUNDLE_NAME) assert bundle.current_tag == "TESTTAG" assert bundle.lib_dir("py") == ( "DATA_DIR/" "adafruit/adafruit-circuitpython-bundle-py/" "adafruit-circuitpython-bundle-py-TESTTAG/lib" ) assert bundle.lib_dir("6mpy") == ( "DATA_DIR/" "adafruit/adafruit-circuitpython-bundle-6mpy/" "adafruit-circuitpython-bundle-6.x-mpy-TESTTAG/lib" ) assert bundle.lib_dir("7mpy") == ( "DATA_DIR/" "adafruit/adafruit-circuitpython-bundle-7mpy/" "adafruit-circuitpython-bundle-7.x-mpy-TESTTAG/lib" )
def test_ensure_latest_bundle_to_update_http_error(): """ If an HTTP error happens during a bundle update, print a friendly error message and exit 1. """ tags_data = {TEST_BUNDLE_NAME: "12345"} with mock.patch("circup.Bundle.latest_tag", "54321"), mock.patch( # "circup.tags_data_load", return_value=tags_data # ), mock.patch( "circup.os.path.isfile", return_value=True, ), mock.patch("circup.open"), mock.patch( "circup.get_bundle", side_effect=requests.exceptions.HTTPError("404") ) as mock_gb, mock.patch( "circup.json" ) as mock_json, mock.patch( "circup.click.secho" ) as mock_click, mock.patch( "circup.sys.exit" ) as mock_exit: circup.Bundle.tags_data = dict() mock_json.load.return_value = tags_data bundle = circup.Bundle(TEST_BUNDLE_NAME) circup.ensure_latest_bundle(bundle) mock_gb.assert_called_once_with(bundle, "54321") assert mock_json.dump.call_count == 0 # not saved. assert mock_click.call_count == 1 # friendly message. mock_exit.assert_called_once_with(1) # exit 1.
def test_get_bundle_versions_avoid_download(): """ When avoid_download is True and lib_dir exists, don't ensure_latest_bundle. Testing both cases: lib_dir exists and lib_dir doesn't exists. """ with mock.patch("circup.ensure_latest_bundle") as mock_elb, mock.patch( "circup.get_modules", return_value={"ok": { "name": "ok" }}) as mock_gm, mock.patch("circup.CPY_VERSION", "4.1.2"), mock.patch( "circup.Bundle.lib_dir", return_value="foo/bar/lib"): bundle = circup.Bundle(TEST_BUNDLE_NAME) bundles_list = [bundle] with mock.patch("circup.os.path.isdir", return_value=True): assert circup.get_bundle_versions(bundles_list, avoid_download=True) == { "ok": { "name": "ok", "bundle": bundle } } assert mock_elb.call_count == 0 mock_gm.assert_called_once_with("foo/bar/lib") with mock.patch("circup.os.path.isdir", return_value=False): assert circup.get_bundle_versions(bundles_list, avoid_download=True) == { "ok": { "name": "ok", "bundle": bundle } } mock_elb.assert_called_once_with(bundle) mock_gm.assert_called_with("foo/bar/lib")
def test_Bundle_init(): """ Create a Bundle and check all the strings are set as expected. """ with mock.patch("circup.logger.info"), mock.patch( "circup.os.path.isfile", return_value=True ), mock.patch("circup.CPY_VERSION", "4.1.2"), mock.patch( "circup.tags_data_load", return_value=dict() ), mock.patch( "circup.DATA_DIR", "DATA_DIR" ): bundle = circup.Bundle(TEST_BUNDLE_NAME) assert repr(bundle) == repr( { "key": TEST_BUNDLE_NAME, "url": "https://github.com/" + TEST_BUNDLE_NAME + "/releases", "urlzip": "adafruit-circuitpython-bundle-{platform}-{tag}.zip", "dir": "DATA_DIR/adafruit/adafruit-circuitpython-bundle-{platform}", "zip": "DATA_DIR/adafruit-circuitpython-bundle-{platform}.zip", "url_format": "https://github.com/" + TEST_BUNDLE_NAME + "/releases/download/{tag}/" "adafruit-circuitpython-bundle-{platform}-{tag}.zip", "current": None, "latest": None, } )
def test_get_bundles_list(): """ Check we are getting the bundles list from BUNDLES_DEFAULT_LIST. """ with mock.patch("circup.BUNDLES_DEFAULT_LIST", [TEST_BUNDLE_NAME]): bundles_list = circup.get_bundles_list() bundle = circup.Bundle(TEST_BUNDLE_NAME) assert repr(bundles_list) == repr([bundle])
def test_get_bundles_list(): """ Check we are getting the bundles list from BUNDLE_CONFIG_FILE. """ with mock.patch("circup.BUNDLE_CONFIG_FILE", TEST_BUNDLE_CONFIG_JSON), mock.patch( "circup.BUNDLE_CONFIG_LOCAL", "" ): bundles_list = circup.get_bundles_list() bundle = circup.Bundle(TEST_BUNDLE_NAME) assert repr(bundles_list) == repr([bundle])
def test_ensure_latest_bundle_no_bundle_data(): """ If there's no BUNDLE_DATA file (containing previous current version of the bundle) then default to update. """ with mock.patch("circup.Bundle.latest_tag", "12345"), mock.patch( "circup.os.path.isfile", return_value=False), mock.patch( "circup.get_bundle") as mock_gb, mock.patch( "circup.json") as mock_json, mock.patch("circup.open"): bundle = circup.Bundle(TEST_BUNDLE_NAME) circup.ensure_latest_bundle(bundle) mock_gb.assert_called_once_with(bundle, "12345") assert mock_json.dump.call_count == 1 # Current version saved to file.
def test_find_modules_goes_bang(): """ Ensure if there's a problem getting metadata an error message is displayed and the utility exists with an error code of 1. """ with mock.patch("circup.get_device_versions", side_effect=Exception("BANG!")), mock.patch( "circup.click") as mock_click, mock.patch( "circup.sys.exit") as mock_exit: bundle = circup.Bundle(TEST_BUNDLE_NAME) bundles_list = [bundle] circup.find_modules("", bundles_list) assert mock_click.echo.call_count == 1 mock_exit.assert_called_once_with(1)
def test_Bundle_latest_tag(): """ Check the latest tag gets through Bundle.latest_tag. """ bundle_data = {TEST_BUNDLE_NAME: "TESTTAG"} with mock.patch("circup.logger.info"), mock.patch( "circup.os.path.isfile", return_value=True), mock.patch( "circup.get_latest_release_from_url", return_value="BESTESTTAG"), mock.patch( "circup.tags_data_load", return_value=bundle_data), mock.patch( "circup.DATA_DIR", "DATA_DIR"): bundle = circup.Bundle(TEST_BUNDLE_NAME) assert bundle.latest_tag == "BESTESTTAG"
def test_ensure_latest_bundle_to_update(): """ If the version found in the BUNDLE_DATA is out of date, then cause an update to the bundle. """ with mock.patch("circup.Bundle.latest_tag", "54321"), mock.patch( "circup.os.path.isfile", return_value=True), mock.patch("circup.open"), mock.patch( "circup.get_bundle") as mock_gb, mock.patch( "circup.json") as mock_json: mock_json.load.return_value = {TEST_BUNDLE_NAME: "12345"} bundle = circup.Bundle(TEST_BUNDLE_NAME) circup.ensure_latest_bundle(bundle) mock_gb.assert_called_once_with(bundle, "54321") assert mock_json.dump.call_count == 1 # Current version saved to file.
def test_Module_outofdate(): """ Ensure the ``outofdate`` property on a Module instance returns the expected boolean value to correctly indicate if the referenced module is, in fact, out of date. """ bundle = circup.Bundle(TEST_BUNDLE_NAME) path = os.path.join("foo", "bar", "baz", "module.py") repo = "https://github.com/adafruit/SomeLibrary.git" m1 = circup.Module(path, repo, "1.2.3", "3.2.1", False, bundle, (None, None)) m2 = circup.Module(path, repo, "1.2.3", "1.2.3", False, bundle, (None, None)) # shouldn't happen! m3 = circup.Module(path, repo, "3.2.1", "1.2.3", False, bundle, (None, None)) assert m1.outofdate is True assert m2.outofdate is False assert m3.outofdate is False
def test_Module_outofdate_bad_versions(): """ Sometimes, the version is not a valid semver value. In this case, the ``outofdate`` property assumes the module should be updated (to correct this problem). Such a problem should be logged. """ bundle = circup.Bundle(TEST_BUNDLE_NAME) path = os.path.join("foo", "bar", "baz", "module.py") repo = "https://github.com/adafruit/SomeLibrary.git" device_version = "hello" bundle_version = "3.2.1" m = circup.Module(path, repo, device_version, bundle_version, False, bundle, (None, None)) with mock.patch("circup.logger.warning") as mock_logger: assert m.outofdate is True assert mock_logger.call_count == 2
def test_Module_update_dir(): """ Ensure if the module is a directory, the expected actions take place to update the module on the connected device. """ bundle = circup.Bundle(TEST_BUNDLE_NAME) path = os.path.join("foo", "bar", "baz", "module.py") repo = "https://github.com/adafruit/SomeLibrary.git" device_version = "1.2.3" bundle_version = None m = circup.Module(path, repo, device_version, bundle_version, False, bundle, (None, None)) with mock.patch("circup.shutil") as mock_shutil, mock.patch( "circup.os.path.isdir", return_value=True): m.update() mock_shutil.rmtree.assert_called_once_with(m.path, ignore_errors=True) mock_shutil.copytree.assert_called_once_with(m.bundle_path, m.path)
def test_ensure_latest_bundle_no_update(): """ If the version found in the BUNDLE_DATA is NOT out of date, just log the fact and don't update. """ with mock.patch("circup.Bundle.latest_tag", "12345"), mock.patch( "circup.os.path.isfile", return_value=True), mock.patch( "circup.os.path.isdir", return_value=True), mock.patch("circup.open"), mock.patch( "circup.get_bundle") as mock_gb, mock.patch( "circup.json") as mock_json, mock.patch( "circup.logger") as mock_logger: mock_json.load.return_value = {TEST_BUNDLE_NAME: "12345"} bundle = circup.Bundle(TEST_BUNDLE_NAME) circup.ensure_latest_bundle(bundle) assert mock_gb.call_count == 0 assert mock_logger.info.call_count == 2
def test_Module_major_update_bad_versions(): """ Sometimes, the version is not a valid semver value. In this case, the ``major_update`` property assumes the module is a major update, so as not to block the user from getting the latest update. Such a problem should be logged. """ bundle = circup.Bundle(TEST_BUNDLE_NAME) path = os.path.join("foo", "bar", "baz", "module.py") repo = "https://github.com/adafruit/SomeLibrary.git" device_version = "1.2.3" bundle_version = "version-3" m = circup.Module(path, repo, device_version, bundle_version, False, bundle, (None, None)) with mock.patch("circup.logger.warning") as mock_logger: assert m.major_update is True assert mock_logger.call_count == 2
def test_Module_row(): """ Ensure the tuple contains the expected items to be correctly displayed in a table of version-related results. """ bundle = circup.Bundle(TEST_BUNDLE_NAME) path = os.path.join("foo", "bar", "baz", "module.py") repo = "https://github.com/adafruit/SomeLibrary.git" with mock.patch("circup.os.path.isfile", return_value=True), mock.patch( "circup.CPY_VERSION", "6.1.2" ): m = circup.Module(path, repo, "1.2.3", None, False, bundle, (None, None)) assert m.row == ("module", "1.2.3", "unknown", "Major Version") m = circup.Module(path, repo, "1.2.3", "1.3.4", False, bundle, (None, None)) assert m.row == ("module", "1.2.3", "1.3.4", "Minor Version") m = circup.Module(path, repo, "1.2.3", "1.2.3", True, bundle, ("9.0.0", None)) assert m.row == ("module", "1.2.3", "1.2.3", "MPY Format")
def test_get_bundle_versions(): """ Ensure get_modules is called with the path for the library bundle. Ensure ensure_latest_bundle is called even if lib_dir exists. """ with mock.patch("circup.ensure_latest_bundle") as mock_elb, mock.patch( "circup.get_modules", return_value={"ok": {"name": "ok"}} ) as mock_gm, mock.patch("circup.CPY_VERSION", "4.1.2"), mock.patch( "circup.Bundle.lib_dir", return_value="foo/bar/lib" ), mock.patch( "circup.os.path.isdir", return_value=True ): bundle = circup.Bundle(TEST_BUNDLE_NAME) bundles_list = [bundle] assert circup.get_bundle_versions(bundles_list) == { "ok": {"name": "ok", "bundle": bundle} } mock_elb.assert_called_once_with(bundle) mock_gm.assert_called_once_with("foo/bar/lib")
def test_ensure_latest_bundle_bad_bundle_data(): """ If there's a BUNDLE_DATA file (containing previous current version of the bundle) but it has been corrupted (which has sometimes happened during manual testing) then default to update. """ with mock.patch("circup.Bundle.latest_tag", "12345"), mock.patch( "circup.os.path.isfile", return_value=True), mock.patch("circup.open"), mock.patch( "circup.get_bundle") as mock_gb, mock.patch( "circup.json.load", side_effect=json.decoder.JSONDecodeError( "BANG!", "doc", 1)), mock.patch("circup.json.dump"), mock.patch( "circup.logger") as mock_logger: bundle = circup.Bundle(TEST_BUNDLE_NAME) circup.ensure_latest_bundle(bundle) mock_gb.assert_called_once_with(bundle, "12345") # wrong file is opened twice (one at __init__, one at save()) assert mock_logger.error.call_count == 2 assert mock_logger.exception.call_count == 2