diff --git a/.vscode/settings.json b/.vscode/settings.json index 30bf8c2d3..fa0a10487 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,4 +8,4 @@ }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts "typescript.tsc.autoDetect": "off" -} \ No newline at end of file +} diff --git a/gulpfile.js b/gulpfile.js index 3c106798d..5a4a494a8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,135 +1,138 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const gulp = require("gulp"); - -const ts = require("gulp-typescript"); -const sourcemaps = require("gulp-sourcemaps"); -const typescript = require("typescript"); -const del = require("del"); -const es = require("event-stream"); -const vsce = require("vsce"); -const nls = require("vscode-nls-dev"); - -const tsProject = ts.createProject("./tsconfig.json", { typescript }); - -const inlineMap = true; -const inlineSource = false; -const outDest = "out"; - -// A list of all locales supported by VSCode can be found here: https://code.visualstudio.com/docs/getstarted/locales -const languages = [{ folderName: "en", id: "en" }]; - -gulp.task("clean", () => { - return del( - [ - "out/*", - "package.nls.*.json", - "../../dist/*0.0.0-UNTRACKEDVERSION.vsix" - ], - { force: true } - ); -}); - -const pythonToMove = [ - "./src/adafruit_circuitplayground/*.*", - "./src/*.py", - "./src/requirements.txt", -]; - -gulp.task("python-compile", () => { - // the base option sets the relative root for the set of files, - // preserving the folder structure - return gulp.src(pythonToMove, { base: "./src/" }).pipe(gulp.dest("out")); -}); - -gulp.task("internal-compile", () => { - return compile(false); -}); - -gulp.task("internal-nls-compile", () => { - return compile(true); -}); - -gulp.task("add-locales", () => { - return gulp - .src(["package.nls.json"]) - .pipe(nls.createAdditionalLanguageFiles(languages, "locales")) - .pipe(gulp.dest(".")); -}); - -gulp.task("vsce:publish", () => { - return vsce.publish(); -}); - -gulp.task("vsce:package", () => { - return vsce.createVSIX({ - packagePath: "../../dist/deviceSimulatorExpress-0.0.0-UNTRACKEDVERSION.vsix" - }); -}); - -gulp.task( - "compile", - gulp.series("clean", "internal-compile", "python-compile", callback => { - callback(); - }) -); - -gulp.task( - "build", - gulp.series( - "clean", - "internal-nls-compile", - "python-compile", - "add-locales", - callback => { - callback(); - } - ) -); - -gulp.task( - "publish", - gulp.series("compile", "vsce:publish", callback => { - callback(); - }) -); - -gulp.task( - "package", - gulp.series("compile", "vsce:package", callback => { - callback(); - }) -); - -//---- internal - -function compile(buildNls) { - var r = tsProject - .src() - .pipe(sourcemaps.init()) - .pipe(tsProject()) - .js.pipe(buildNls ? nls.rewriteLocalizeCalls() : es.through()) - .pipe( - buildNls - ? nls.createAdditionalLanguageFiles(languages, "locales", "out") - : es.through() - ); - - if (inlineMap && inlineSource) { - r = r.pipe(sourcemaps.write()); - } else { - r = r.pipe( - sourcemaps.write("../out", { - // no inlined source - includeContent: inlineSource, - // Return relative source map root directories per file. - sourceRoot: "../src" - }) - ); - } - - return r.pipe(gulp.dest(outDest)); -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const gulp = require("gulp"); + +const ts = require("gulp-typescript"); +const sourcemaps = require("gulp-sourcemaps"); +const typescript = require("typescript"); +const del = require("del"); +const es = require("event-stream"); +const vsce = require("vsce"); +const nls = require("vscode-nls-dev"); + +const tsProject = ts.createProject("./tsconfig.json", { typescript }); + +const inlineMap = true; +const inlineSource = false; +const outDest = "out"; + +// A list of all locales supported by VSCode can be found here: https://code.visualstudio.com/docs/getstarted/locales +const languages = [{ folderName: "en", id: "en" }]; + +gulp.task("clean", () => { + return del( + [ + "out/*", + "package.nls.*.json", + "../../dist/*0.0.0-UNTRACKEDVERSION.vsix", + ], + { force: true } + ); +}); + +const pythonToMove = [ + "./src/adafruit_circuitplayground/*.*", + "./src/microbit/*.*", + "./src/microbit/!(test)/**/*", + "./src/*.py", + "./src/requirements.txt", +]; + +gulp.task("python-compile", () => { + // the base option sets the relative root for the set of files, + // preserving the folder structure + return gulp.src(pythonToMove, { base: "./src/" }).pipe(gulp.dest("out")); +}); + +gulp.task("internal-compile", () => { + return compile(false); +}); + +gulp.task("internal-nls-compile", () => { + return compile(true); +}); + +gulp.task("add-locales", () => { + return gulp + .src(["package.nls.json"]) + .pipe(nls.createAdditionalLanguageFiles(languages, "locales")) + .pipe(gulp.dest(".")); +}); + +gulp.task("vsce:publish", () => { + return vsce.publish(); +}); + +gulp.task("vsce:package", () => { + return vsce.createVSIX({ + packagePath: + "../../dist/deviceSimulatorExpress-0.0.0-UNTRACKEDVERSION.vsix", + }); +}); + +gulp.task( + "compile", + gulp.series("clean", "internal-compile", "python-compile", callback => { + callback(); + }) +); + +gulp.task( + "build", + gulp.series( + "clean", + "internal-nls-compile", + "python-compile", + "add-locales", + callback => { + callback(); + } + ) +); + +gulp.task( + "publish", + gulp.series("compile", "vsce:publish", callback => { + callback(); + }) +); + +gulp.task( + "package", + gulp.series("compile", "vsce:package", callback => { + callback(); + }) +); + +//---- internal + +function compile(buildNls) { + var r = tsProject + .src() + .pipe(sourcemaps.init()) + .pipe(tsProject()) + .js.pipe(buildNls ? nls.rewriteLocalizeCalls() : es.through()) + .pipe( + buildNls + ? nls.createAdditionalLanguageFiles(languages, "locales", "out") + : es.through() + ); + + if (inlineMap && inlineSource) { + r = r.pipe(sourcemaps.write()); + } else { + r = r.pipe( + sourcemaps.write("../out", { + // no inlined source + includeContent: inlineSource, + // Return relative source map root directories per file. + sourceRoot: "../src", + }) + ); + } + + return r.pipe(gulp.dest(outDest)); +} diff --git a/src/microbit/__init__.py b/src/microbit/__init__.py new file mode 100644 index 000000000..a0d5418e9 --- /dev/null +++ b/src/microbit/__init__.py @@ -0,0 +1 @@ +from .shim import * diff --git a/src/microbit/model/button.py b/src/microbit/model/button.py new file mode 100644 index 000000000..54a60e8a4 --- /dev/null +++ b/src/microbit/model/button.py @@ -0,0 +1,27 @@ +class Button: + # The implementation is based off of https://github.com/bbcmicrobit/micropython/blob/master/docs/button.rst. + def __init__(self): + self.__pressed = False + self.__presses = 0 + self.__prev_pressed = False + + def is_pressed(self): + return self.__pressed + + def was_pressed(self): + res = self.__prev_pressed + self.__prev_pressed = False + return res + + def get_presses(self): + res = self.__presses + self.__presses = 0 + return res + + def __press_down(self): + self.__pressed = True + self.__prev_pressed = True + self.__presses += 1 + + def __release(self): + self.__pressed = False diff --git a/src/microbit/model/microbit_model.py b/src/microbit/model/microbit_model.py new file mode 100644 index 000000000..f8e2572da --- /dev/null +++ b/src/microbit/model/microbit_model.py @@ -0,0 +1,21 @@ +import time + +from .button import Button + + +class MicrobitModel: + def __init__(self): + # State in the Python process + self.button_a = Button() + self.button_b = Button() + self.__start_time = time.time() + + def sleep(self, n): + time.sleep(n / 1000) + + def running_time(self): + print(f"time. time: {time.time()}") + return time.time() - self.__start_time + + +mb = MicrobitModel() diff --git a/src/microbit/shim.py b/src/microbit/shim.py new file mode 100644 index 000000000..ac8f544f0 --- /dev/null +++ b/src/microbit/shim.py @@ -0,0 +1,9 @@ +from .model import microbit_model + + +def sleep(n): + microbit_model.mb.sleep(n) + + +def running_time(): + microbit_model.mb.running_time() diff --git a/src/microbit/test/__init__.py b/src/microbit/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/microbit/test/test_button.py b/src/microbit/test/test_button.py new file mode 100644 index 000000000..aee9cbb43 --- /dev/null +++ b/src/microbit/test/test_button.py @@ -0,0 +1,46 @@ +import pytest +from ..model.button import Button + + +class TestButton(object): + def setup_method(self): + self.button = Button() + + def test_press_down(self): + self.button._Button__press_down() + assert self.button._Button__presses == 1 + assert self.button._Button__pressed + assert self.button._Button__prev_pressed + self.button._Button__press_down() + assert self.button._Button__presses == 2 + assert self.button._Button__pressed + assert self.button._Button__prev_pressed + + def test_release(self): + self.button._Button__pressed = True + self.button._Button__prev_pressed = False + self.button._Button__release() + assert not self.button._Button__pressed + + def test_is_pressed(self): + assert not self.button.is_pressed() + self.button._Button__press_down() + assert self.button.is_pressed() + + def test_was_pressed(self): + assert not self.button.was_pressed() + self.button._Button__press_down() + self.button._Button__release() + assert self.button.was_pressed() + # Button resets __prev_pressed after was_pressed() is called. + assert not self.button.was_pressed() + + @pytest.mark.parametrize("presses", [1, 2, 4]) + def test_get_presses(self, presses): + assert 0 == self.button.get_presses() + for i in range(presses): + self.button._Button__press_down() + self.button._Button__release() + assert presses == self.button.get_presses() + # Presses is reset to 0 after get_presses() is called. + assert 0 == self.button.get_presses() diff --git a/src/microbit/test/test_microbit_model.py b/src/microbit/test/test_microbit_model.py new file mode 100644 index 000000000..e37df6019 --- /dev/null +++ b/src/microbit/test/test_microbit_model.py @@ -0,0 +1,24 @@ +import time + +import pytest +from unittest import mock +from ..model.microbit_model import MicrobitModel + + +class TestMicrobitModel(object): + def setup_method(self): + self.mb = MicrobitModel() + + @pytest.mark.parametrize("value", [9, 30, 1999]) + def test_sleep(self, value): + time.sleep = mock.Mock() + self.mb.sleep(value) + time.sleep.assert_called_with(value / 1000) + + def test_running_time(self): + mock_start_time = 10 + mock_end_time = 300 + self.mb._MicrobitModel__start_time = mock_start_time + time.time = mock.MagicMock(return_value=mock_end_time) + print(time.time()) + assert mock_end_time - mock_start_time == pytest.approx(self.mb.running_time()) diff --git a/src/microbit/test/test_shim.py b/src/microbit/test/test_shim.py new file mode 100644 index 000000000..68853ec9b --- /dev/null +++ b/src/microbit/test/test_shim.py @@ -0,0 +1,19 @@ +import time + +import pytest +from unittest import mock +from .. import shim +from ..model import microbit_model + + +class TestShim(object): + def test_sleep(self): + milliseconds = 100 + microbit_model.mb.sleep = mock.Mock() + shim.sleep(milliseconds) + microbit_model.mb.sleep.assert_called_with(milliseconds) + + def test_running_time(self): + microbit_model.mb.running_time = mock.Mock() + shim.running_time() + microbit_model.mb.running_time.assert_called_once()