diff --git a/platformio/builder/tools/piotest.py b/platformio/builder/tools/piotest.py index 4ed542ae..e79584a3 100644 --- a/platformio/builder/tools/piotest.py +++ b/platformio/builder/tools/piotest.py @@ -14,46 +14,7 @@ from __future__ import absolute_import -import atexit -import sys -from os import remove -from os.path import isdir, isfile, join, sep -from string import Template - -FRAMEWORK_PARAMETERS = { - "arduino": { - "framework": "Arduino.h", - "serial_obj": "", - "serial_putc": "Serial.write(a)", - "serial_flush": "Serial.flush()", - "serial_begin": "Serial.begin(9600)", - "serial_end": "Serial.end()" - }, - "mbed": { - "framework": "mbed.h", - "serial_obj": "Serial pc(USBTX, USBRX);", - "serial_putc": "pc.putc(a)", - "serial_flush": "", - "serial_begin": "pc.baud(9600)", - "serial_end": "" - }, - "energia": { - "framework": "Energia.h", - "serial_obj": "", - "serial_putc": "Serial.write(a)", - "serial_flush": "Serial.flush()", - "serial_begin": "Serial.begin(9600)", - "serial_end": "Serial.end()" - }, - "native": { - "framework": "stdio.h", - "serial_obj": "", - "serial_putc": "putchar(a)", - "serial_flush": "fflush(stdout)", - "serial_begin": "", - "serial_end": "" - } -} +from os.path import join, sep def ProcessTest(env): @@ -69,79 +30,16 @@ def ProcessTest(env): env.PioPlatform().get_package_dir("tool-unity")) env.Prepend(LIBS=[unitylib]) - test_dir = env.subst("$PROJECTTEST_DIR") - env.GenerateOutputReplacement(test_dir) src_filter = None if "PIOTEST" in env: src_filter = "+" src_filter += " +<%s%s>" % (env['PIOTEST'], sep) return env.CollectBuildFiles( - "$BUILDTEST_DIR", test_dir, src_filter=src_filter, duplicate=False) - - -def GenerateOutputReplacement(env, destination_dir): - - if not isdir(env.subst(destination_dir)): - sys.stderr.write( - "Error: Test folder does not exist. Please put your test suite " - "to \"test\" folder in project's root directory.\n") - env.Exit(1) - - TEMPLATECPP = """ -# include <$framework> -# include - -$serial_obj - -void output_char(int a) -{ - $serial_putc; -} - -void output_flush(void) -{ - $serial_flush; -} - -void output_start(unsigned int baudrate) -{ - $serial_begin; -} - -void output_complete(void) -{ - $serial_end; -} - -""" - - def delete_tmptest_file(file_): - try: - remove(file_) - except: # pylint: disable=bare-except - if isfile(file_): - print("Warning: Could not remove temporary file '%s'. " - "Please remove it manually." % file_) - - if env['PIOPLATFORM'] == "native": - framework = "native" - else: - framework = env.subst("$PIOFRAMEWORK").lower() - if framework not in FRAMEWORK_PARAMETERS: - sys.stderr.write( - "Error: %s framework doesn't support testing feature!\n" % - framework) - env.Exit(1) - else: - data = Template(TEMPLATECPP).substitute(FRAMEWORK_PARAMETERS[ - framework]) - - tmp_file = join(destination_dir, "output_export.cpp") - with open(tmp_file, "w") as f: - f.write(data) - - atexit.register(delete_tmptest_file, tmp_file) + "$BUILDTEST_DIR", + "$PROJECTTEST_DIR", + src_filter=src_filter, + duplicate=False) def exists(_): @@ -150,5 +48,4 @@ def exists(_): def generate(env): env.AddMethod(ProcessTest) - env.AddMethod(GenerateOutputReplacement) return env diff --git a/platformio/commands/test.py b/platformio/commands/test.py index 6820fa31..420c7082 100644 --- a/platformio/commands/test.py +++ b/platformio/commands/test.py @@ -12,20 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=R0913,R0914 - -from fnmatch import fnmatch -from os import getcwd, listdir -from os.path import isdir, join -from time import sleep, time +import sys +from os import getcwd import click -import serial -from platformio import exception, util -from platformio.commands.run import cli as cmd_run -from platformio.commands.run import check_project_envs, print_header -from platformio.managers.platform import PlatformFactory +from platformio.pioplus import pioplus_call @click.command("test", short_help="Unit Testing") @@ -43,220 +35,5 @@ from platformio.managers.platform import PlatformFactory writable=True, resolve_path=True)) @click.option("--verbose", "-v", is_flag=True) -@click.pass_context -def cli(ctx, environment, ignore, upload_port, project_dir, verbose): - with util.cd(project_dir): - test_dir = util.get_projecttest_dir() - if not isdir(test_dir): - raise exception.TestDirEmpty(test_dir) - test_names = get_test_names(test_dir) - projectconf = util.load_project_config() - assert check_project_envs(projectconf, environment) - - click.echo("Verbose mode can be enabled via `-v, --verbose` option") - click.echo("Collected %d items" % len(test_names)) - - start_time = time() - results = [] - for testname in test_names: - for section in projectconf.sections(): - if not section.startswith("env:"): - continue - - envname = section[4:] - if environment and envname not in environment: - continue - - # check ignore patterns - _ignore = list(ignore) - if projectconf.has_option(section, "test_ignore"): - _ignore.extend([ - p.strip() - for p in projectconf.get(section, "test_ignore").split(",") - if p.strip() - ]) - if testname != "*" and \ - any([fnmatch(testname, p) for p in _ignore]): - results.append((None, testname, envname)) - continue - - cls = (LocalTestProcessor - if projectconf.get(section, "platform") == "native" else - EmbeddedTestProcessor) - tp = cls(ctx, testname, envname, { - "project_config": projectconf, - "project_dir": project_dir, - "upload_port": upload_port, - "verbose": verbose - }) - results.append((tp.process(), testname, envname)) - - click.echo() - print_header("[%s]" % click.style("TEST SUMMARY")) - - passed = True - for result in results: - status, testname, envname = result - status_str = click.style("PASSED", fg="green") - if status is False: - passed = False - status_str = click.style("FAILED", fg="red") - elif status is None: - status_str = click.style("IGNORED", fg="yellow") - - click.echo( - "test:%s/env:%s\t[%s]" % (click.style( - testname, fg="yellow"), click.style( - envname, fg="cyan"), status_str), - err=status is False) - - print_header( - "[%s] Took %.2f seconds" % ((click.style( - "PASSED", fg="green", bold=True) if passed else click.style( - "FAILED", fg="red", bold=True)), time() - start_time), - is_error=not passed) - - if not passed: - raise exception.ReturnErrorCode() - - -class TestProcessorBase(object): - - def __init__(self, cmd_ctx, testname, envname, options): - self.cmd_ctx = cmd_ctx - self.cmd_ctx.meta['piotest_processor'] = True - self.test_name = testname - self.env_name = envname - self.options = options - self._run_failed = False - - def print_progress(self, text, is_error=False): - click.echo() - print_header( - "[test::%s] %s" % (click.style( - self.test_name, fg="yellow", bold=True), text), - is_error=is_error) - - def build_or_upload(self, target): - if self.test_name != "*": - self.cmd_ctx.meta['piotest'] = self.test_name - return self.cmd_ctx.invoke( - cmd_run, - project_dir=self.options['project_dir'], - upload_port=self.options['upload_port'], - silent=not self.options['verbose'], - environment=[self.env_name], - target=target) - - def run(self): - raise NotImplementedError - - def on_run_out(self, line): - if line.endswith(":PASS"): - click.echo("%s\t[%s]" % (line[:-5], click.style( - "PASSED", fg="green"))) - elif ":FAIL:" in line: - self._run_failed = True - click.echo("%s\t[%s]" % (line, click.style("FAILED", fg="red"))) - else: - click.echo(line) - - -class LocalTestProcessor(TestProcessorBase): - - def process(self): - self.print_progress("Building... (1/2)") - self.build_or_upload(["test"]) - self.print_progress("Testing... (2/2)") - return self.run() - - def run(self): - with util.cd(self.options['project_dir']): - pioenvs_dir = util.get_projectpioenvs_dir() - result = util.exec_command( - [join(pioenvs_dir, self.env_name, "program")], - stdout=util.AsyncPipe(self.on_run_out), - stderr=util.AsyncPipe(self.on_run_out)) - assert "returncode" in result - return result['returncode'] == 0 and not self._run_failed - - -class EmbeddedTestProcessor(TestProcessorBase): - - SERIAL_TIMEOUT = 600 - SERIAL_BAUDRATE = 9600 - - def process(self): - self.print_progress("Building... (1/3)") - self.build_or_upload(["test"]) - self.print_progress("Uploading... (2/3)") - self.build_or_upload(["test", "upload"]) - self.print_progress("Testing... (3/3)") - sleep(1.0) # wait while board is starting... - return self.run() - - def run(self): - click.echo("If you don't see any output for the first 10 secs, " - "please reset board (press reset button)") - click.echo() - ser = serial.Serial( - self.get_serial_port(), - self.SERIAL_BAUDRATE, - timeout=self.SERIAL_TIMEOUT) - while True: - line = ser.readline().strip() - - # fix non-ascii output from device - for i, c in enumerate(line[::-1]): - if ord(c) > 127: - line = line[-i:] - break - - if not line: - continue - self.on_run_out(line) - if all([l in line for l in ("Tests", "Failures", "Ignored")]): - break - ser.close() - return not self._run_failed - - def get_serial_port(self): - config = self.options['project_config'] - envdata = {} - for k, v in config.items("env:" + self.env_name): - envdata[k] = v - - # if upload port is specified manually - if self.options.get("upload_port", envdata.get("upload_port")): - return self.options.get("upload_port", envdata.get("upload_port")) - - p = PlatformFactory.newPlatform(envdata['platform']) - bconfig = p.board_config(envdata['board']) - - port = None - board_hwids = [] - if "build.hwids" in bconfig: - board_hwids = bconfig.get("build.hwids") - for item in util.get_serialports(): - if "VID:PID" not in item['hwid']: - continue - port = item['port'] - for hwid in board_hwids: - hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "") - if hwid_str in item['hwid']: - return port - if not port: - raise exception.PlatformioException( - "Please specify `upload_port` for environment or use " - "global `--upload-port` option.") - return port - - -def get_test_names(test_dir): - names = [] - for item in sorted(listdir(test_dir)): - if isdir(join(test_dir, item)): - names.append(item) - if not names: - names = ["*"] - return names +def cli(*args, **kwargs): # pylint: disable=unused-argument + pioplus_call(sys.argv[1:]) diff --git a/platformio/commands/update.py b/platformio/commands/update.py index 422ac3d9..2d32ef48 100644 --- a/platformio/commands/update.py +++ b/platformio/commands/update.py @@ -17,6 +17,7 @@ import click from platformio.commands.lib import lib_update as cmd_lib_update from platformio.commands.platform import platform_update as cmd_platform_update from platformio.managers.lib import LibraryManager +from platformio.pioplus import pioplus_update @click.command( @@ -31,6 +32,7 @@ def cli(ctx, only_check): click.echo("Platform Manager") click.echo("================") ctx.invoke(cmd_platform_update, only_check=only_check) + pioplus_update() click.echo() click.echo("Library Manager") diff --git a/platformio/maintenance.py b/platformio/maintenance.py index 0095beaa..2d2605c4 100644 --- a/platformio/maintenance.py +++ b/platformio/maintenance.py @@ -29,6 +29,7 @@ from platformio.commands.platform import platform_update as cmd_platform_update from platformio.commands.upgrade import get_latest_version from platformio.managers.lib import LibraryManager from platformio.managers.platform import PlatformManager +from platformio.pioplus import pioplus_update def in_silence(ctx): @@ -140,6 +141,9 @@ def after_upgrade(ctx): # pm.update(manifest['name'], "^" + manifest['version']) pm.update(manifest['name']) + # update PlatformIO Plus tool if installed + pioplus_update() + click.secho( "PlatformIO has been successfully upgraded to %s!\n" % __version__, diff --git a/platformio/pioplus.py b/platformio/pioplus.py new file mode 100644 index 00000000..aae5c646 --- /dev/null +++ b/platformio/pioplus.py @@ -0,0 +1,53 @@ +# Copyright 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import subprocess +from os.path import join + +from platformio import util +from platformio.managers.package import PackageManager + +PACKAGE_PIOPLUS_NAME = "tool-pioplus" + + +class PioPlusPackageManager(PackageManager): + + def __init__(self): + PackageManager.__init__( + self, join(util.get_home_dir(), "packages"), + ["https://dl.bintray.com/platformio/dl-packages/manifest.json", + "https://dl.platformio.org/packages/manifest.json"]) + + +def get_pioplusexe_path(): + pm = PioPlusPackageManager() + package_dir = pm.get_package_dir(PACKAGE_PIOPLUS_NAME) + if not package_dir: + pm.install(PACKAGE_PIOPLUS_NAME) + package_dir = pm.get_package_dir(PACKAGE_PIOPLUS_NAME) + assert package_dir + return join(package_dir, "pioplus") + + +def pioplus_update(): + pm = PioPlusPackageManager() + if pm.get_package_dir(PACKAGE_PIOPLUS_NAME): + pm.update(PACKAGE_PIOPLUS_NAME) + + +def pioplus_call(args, **kwargs): + os.environ['PYTHONEXEPATH'] = util.get_pythonexe_path() + util.copy_pythonpath_to_osenv() + subprocess.call([get_pioplusexe_path()] + args, **kwargs) diff --git a/tests/commands/test_test.py b/tests/commands/test_test.py index 482f20f4..1ecaac39 100644 --- a/tests/commands/test_test.py +++ b/tests/commands/test_test.py @@ -21,6 +21,6 @@ def test_local_env(clirunner, validate_cliresult): result = clirunner.invoke( cli_test, ["-d", join("examples", "unit-testing", "calculator"), "-e", "local"]) - result.exit_code == -1 + validate_cliresult(result) assert all( [s in result.output for s in ("[PASSED]", "[IGNORED]", "[FAILED]")]) diff --git a/tox.ini b/tox.ini index d2fffe23..13c4e891 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ # limitations under the License. [tox] -envlist = py26, py27, docs, lint +envlist = py27, docs, lint [testenv:develop] basepython = python2.7 @@ -53,10 +53,7 @@ commands = pylint --rcfile=./.pylintrc ./platformio [testenv] -basepython = - py26: python2.6 - py27: python2.7 -usedevelop = True +basepython = python2.7 passenv = * deps = pytest @@ -65,8 +62,8 @@ commands = py.test -v --basetemp="{envtmpdir}" tests [testenv:coverage] -passenv = * basepython = python2.7 +passenv = * deps = pytest pytest-cov