mirror of
https://github.com/platformio/platformio-core.git
synced 2025-07-29 17:47:14 +02:00
Unit Testing for Embedded // Resolve #408
This commit is contained in:
@ -10,7 +10,8 @@ PlatformIO 3.0
|
||||
* Decentralized architecture for development platforms: "platform.json",
|
||||
semantic versioning, package dependencies, embedded board configs, isolated
|
||||
build scripts
|
||||
(`issue #479 <https://github.com/platformio/platformio/issues/479>`_)
|
||||
* Unit Testing for Embedded (`docs <http://docs.platformio.org/en/latest/platforms/unit_testing.html>`__)
|
||||
(`issue #408 <https://github.com/platformio/platformio/issues/408>`_)
|
||||
|
||||
PlatformIO 2.0
|
||||
--------------
|
||||
|
@ -69,6 +69,10 @@ Allows to override :ref:`projectconf` option :ref:`projectconf_pio_envs_dir`.
|
||||
|
||||
Allows to override :ref:`projectconf` option :ref:`projectconf_pio_data_dir`.
|
||||
|
||||
.. envvar:: PLATFORMIO_TEST_DIR
|
||||
|
||||
Allows to override :ref:`projectconf` option :ref:`projectconf_pio_test_dir`.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
@ -111,6 +111,7 @@ Contents
|
||||
platforms/embedded_boards
|
||||
frameworks/index
|
||||
platforms/custom_platform_and_board
|
||||
platforms/unit_testing
|
||||
|
||||
.. toctree::
|
||||
:caption: Library Manager
|
||||
|
390
docs/platforms/unit_testing.rst
Normal file
390
docs/platforms/unit_testing.rst
Normal file
@ -0,0 +1,390 @@
|
||||
.. Copyright 2014-present Ivan Kravets <me@ikravets.com>
|
||||
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.
|
||||
|
||||
.. _unit_testing:
|
||||
|
||||
Unit Testing
|
||||
============
|
||||
|
||||
`Unit Testing (wiki) <https://en.wikipedia.org/wiki/Unit_testing>`_
|
||||
is a software testing method by which individual units of source code, sets
|
||||
of one or more MCU program modules together with associated control data,
|
||||
usage procedures, and operating procedures, are tested to determine whether
|
||||
they are fit for use. Unit testing finds problems early in the development cycle.
|
||||
|
||||
PlatformIO Test System is very interesting for embedded development.
|
||||
It allows you to write tests locally and run them directly on the target
|
||||
device (hardware unit testing). Also, you will be able to run the same tests
|
||||
on the different target devices (:ref:`embedded_boards`).
|
||||
|
||||
PlatformIO Test System consists of:
|
||||
|
||||
* Project builder
|
||||
* Test builder
|
||||
* Firmware uploader
|
||||
* Test processor
|
||||
|
||||
.. contents::
|
||||
|
||||
.. _unit_testing_design:
|
||||
|
||||
Design
|
||||
------
|
||||
|
||||
PlatformIO Test System design is based on a few isolated components:
|
||||
|
||||
1. **Main program**. Contains the independent modules, procedures,
|
||||
functions or methods that will be the target candidates (TC) for testing
|
||||
2. **Unit test**. This a small independent program that is intended to
|
||||
re-use TC from the main program and apply tests for them.
|
||||
3. **Test processor**. The set of approaches and tools that will be used
|
||||
to apply test for the environments from :ref:`projectconf`.
|
||||
|
||||
Workflow
|
||||
--------
|
||||
|
||||
1. Create PlatformIO project using :ref:`cmd_init` command
|
||||
2. Place source code of main program to ``src`` directory
|
||||
3. Wrap ``main()`` or ``setup()/loop()`` methods of main program in ``UNIT_TEST``
|
||||
guard:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/**
|
||||
* Arduino Wiring-based Framework
|
||||
*/
|
||||
#ifndef UNIT_TEST
|
||||
void setup () {
|
||||
// some code...
|
||||
}
|
||||
|
||||
void loop () {
|
||||
// some code...
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Generic C/C++
|
||||
*/
|
||||
#ifndef UNIT_TEST
|
||||
int main() {
|
||||
// setup code...
|
||||
|
||||
while (1) {
|
||||
// loop code...
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
4. Create ``test`` directory in the root of project. See :ref:`projectconf_pio_test_dir`
|
||||
5. Write test using :ref:`unit_testing_api`. The each test is a small
|
||||
independent program with own ``main()`` or ``setup()/loop()`` methods. Also,
|
||||
test should start from ``UNITY_BEGIN()`` and finish with ``UNITY_END()``
|
||||
6. Place test to ``test`` directory. If you have more than one test, split them
|
||||
into sub-folders. For example, ``test/test_1/*.[c,cpp,h]``,
|
||||
``test_N/*.[c,cpp,h]``, etc. If no such directory in ``test`` folder, then
|
||||
PlatformIO Test System will treat the source code of ``test`` folder
|
||||
as SINGLE test.
|
||||
7. Run tests using :ref:`cmd_test` command.
|
||||
|
||||
.. _unit_testing_api:
|
||||
|
||||
Test API
|
||||
--------
|
||||
|
||||
The summary of `Unity Test API <https://github.com/ThrowTheSwitch/Unity#unity-test-api>`_:
|
||||
|
||||
* `Running Tests <https://github.com/ThrowTheSwitch/Unity#running-tests>`_
|
||||
|
||||
- ``RUN_TEST(func, linenum)``
|
||||
|
||||
* `Ignoring Tests <https://github.com/ThrowTheSwitch/Unity#ignoring-tests>`_
|
||||
|
||||
- ``TEST_IGNORE()``
|
||||
- ``TEST_IGNORE_MESSAGE (message)``
|
||||
|
||||
* `Aborting Tests <https://github.com/ThrowTheSwitch/Unity#aborting-tests>`_
|
||||
|
||||
- ``TEST_PROTECT()``
|
||||
- ``TEST_ABORT()``
|
||||
|
||||
* `Basic Validity Tests <https://github.com/ThrowTheSwitch/Unity#basic-validity-tests>`_
|
||||
|
||||
- ``TEST_ASSERT_TRUE(condition)``
|
||||
- ``TEST_ASSERT_FALSE(condition)``
|
||||
- ``TEST_ASSERT(condition)``
|
||||
- ``TEST_ASSERT_UNLESS(condition)``
|
||||
- ``TEST_FAIL()``
|
||||
- ``TEST_FAIL_MESSAGE(message)``
|
||||
|
||||
* `Numerical Assertions: Integers <https://github.com/ThrowTheSwitch/Unity#numerical-assertions-integers>`_
|
||||
|
||||
- ``TEST_ASSERT_EQUAL_INT(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_INT8(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_INT16(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_INT32(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_INT64(expected, actual)``
|
||||
|
||||
- ``TEST_ASSERT_EQUAL_UINT(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_UINT8(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_UINT16(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_UINT32(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_UINT64(expected, actual)``
|
||||
|
||||
- ``TEST_ASSERT_EQUAL_HEX(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_HEX8(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_HEX16(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_HEX32(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_HEX64(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, elements)``
|
||||
|
||||
- ``TEST_ASSERT_EQUAL(expected, actual)``
|
||||
- ``TEST_ASSERT_INT_WITHIN(delta, expected, actual)``
|
||||
|
||||
* `Numerical Assertions: Bitwise <https://github.com/ThrowTheSwitch/Unity#numerical-assertions-bitwise>`_
|
||||
|
||||
- ``TEST_ASSERT_BITS(mask, expected, actual)``
|
||||
- ``TEST_ASSERT_BITS_HIGH(mask, actual)``
|
||||
- ``TEST_ASSERT_BITS_LOW(mask, actual)``
|
||||
- ``TEST_ASSERT_BIT_HIGH(mask, actual)``
|
||||
- ``TEST_ASSERT_BIT_LOW(mask, actual)``
|
||||
|
||||
* `Numerical Assertions: Floats <https://github.com/ThrowTheSwitch/Unity#numerical-assertions-floats>`_
|
||||
|
||||
- ``TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_FLOAT(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_DOUBLE(expected, actual)``
|
||||
|
||||
* `String Assertions <https://github.com/ThrowTheSwitch/Unity#string-assertions>`_
|
||||
|
||||
- ``TEST_ASSERT_EQUAL_STRING(expected, actual)``
|
||||
- ``TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len)``
|
||||
- ``TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message)``
|
||||
- ``TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message)``
|
||||
|
||||
* `Pointer Assertions <https://github.com/ThrowTheSwitch/Unity#pointer-assertions>`_
|
||||
|
||||
- ``TEST_ASSERT_NULL(pointer)``
|
||||
- ``TEST_ASSERT_NOT_NULL(pointer)``
|
||||
|
||||
* `Memory Assertions <https://github.com/ThrowTheSwitch/Unity#pointer-assertions>`_
|
||||
|
||||
- ``TEST_ASSERT_EQUAL_MEMORY(expected, actual, len)``
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
1. Please follow to :ref:`quickstart` and create "Blink Project". According
|
||||
to the Unit Testing :ref:`unit_testing_design` it is the **Main program**
|
||||
2. Create ``test`` directory in that project (on the same level as ``src``)
|
||||
and place ``test_main.cpp`` file to it (the source code is located below)
|
||||
3. Wrap ``setup()`` and ``loop()`` methods of main program in ``UNIT_TEST``
|
||||
guard
|
||||
4. Run tests using :ref:`cmd_test` command.
|
||||
|
||||
Project structure
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
project_dir
|
||||
├── lib
|
||||
│ └── readme.txt
|
||||
├── platformio.ini
|
||||
├── src
|
||||
│ └── main.cpp
|
||||
└── test
|
||||
└── test_main.cpp
|
||||
|
||||
Source files
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* ``platformio.ini``
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
; Project Configuration File
|
||||
; Docs: http://docs.platformio.org/en/latest/projectconf.html
|
||||
|
||||
[env:uno]
|
||||
platform = atmelavr
|
||||
framework = arduino
|
||||
board = uno
|
||||
|
||||
[env:nodemcu]
|
||||
platform = espressif
|
||||
framework = arduino
|
||||
board = nodemcu
|
||||
|
||||
[env:teensy31]
|
||||
platform = teensy
|
||||
framework = arduino
|
||||
board = teensy31
|
||||
|
||||
* ``src/main.cpp``
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/*
|
||||
* Blink
|
||||
* Turns on an LED on for one second,
|
||||
* then off for one second, repeatedly.
|
||||
*/
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#ifndef UNIT_TEST // IMPORTANT LINE!
|
||||
|
||||
void setup()
|
||||
{
|
||||
// initialize LED digital pin as an output.
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// turn the LED on (HIGH is the voltage level)
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
// wait for a second
|
||||
delay(1000);
|
||||
// turn the LED off by making the voltage LOW
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
// wait for a second
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
#endif // IMPORTANT LINE!
|
||||
|
||||
* ``test/test_main.cpp``
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <unity.h>
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
|
||||
// void setUp(void) {
|
||||
// // set stuff up here
|
||||
// }
|
||||
|
||||
// void tearDown(void) {
|
||||
// // clean stuff up here
|
||||
// }
|
||||
|
||||
void test_led_builtin_pin_number(void) {
|
||||
TEST_ASSERT_EQUAL(LED_BUILTIN, 13);
|
||||
}
|
||||
|
||||
void test_led_state_high(void) {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
TEST_ASSERT_EQUAL(digitalRead(LED_BUILTIN), HIGH);
|
||||
}
|
||||
|
||||
void test_led_state_low(void) {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
TEST_ASSERT_EQUAL(digitalRead(LED_BUILTIN), LOW);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
UNITY_BEGIN(); // IMPORTANT LINE!
|
||||
RUN_TEST(test_led_builtin_pin_number);
|
||||
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
}
|
||||
|
||||
uint8_t i = 0;
|
||||
uint8_t max_blinks = 5;
|
||||
|
||||
void loop() {
|
||||
if (i < max_blinks)
|
||||
{
|
||||
RUN_TEST(test_led_state_high);
|
||||
delay(500);
|
||||
RUN_TEST(test_led_state_low);
|
||||
delay(500);
|
||||
}
|
||||
else if (i == max_blinks) {
|
||||
UNITY_END(); // IMPORTANT LINE!
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Test results
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
> platformio test --environment uno
|
||||
Collected 1 items
|
||||
|
||||
========================= [test::*] Building... (1/3) ==============================
|
||||
|
||||
[Wed Jun 15 00:27:42 2016] Processing uno (platform: atmelavr, board: uno, framework: arduino)
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
avr-g++ -o .pioenvs/uno/test/test_main.o -c -fno-exceptions -fno-threadsafe-statics -std=gnu++11 -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DPLATFORMIO=030000 -DARDUINO_ARCH_AVR -DARDUINO_AVR_UNO -DARDUINO=10608 -DUNIT_TEST -DUNITY_INCLUDE_CONFIG_H -I.pioenvs/uno/FrameworkArduino -I.pioenvs/uno/FrameworkArduinoVariant -Isrc -I.pioenvs/uno/UnityTestLib test/test_main.cpp
|
||||
avr-g++ -o .pioenvs/uno/firmware.elf -Os -mmcu=atmega328p -Wl,--gc-sections,--relax .pioenvs/uno/src/main.o .pioenvs/uno/test/output_export.o .pioenvs/uno/test/test_main.o -L.pioenvs/uno -Wl,--start-group .pioenvs/uno/libUnityTestLib.a .pioenvs/uno/libFrameworkArduinoVariant.a .pioenvs/uno/libFrameworkArduino.a -lm -Wl,--end-group
|
||||
avr-objcopy -O ihex -R .eeprom .pioenvs/uno/firmware.elf .pioenvs/uno/firmware.hex
|
||||
avr-size --mcu=atmega328p -C -d .pioenvs/uno/firmware.elf
|
||||
AVR Memory Usage
|
||||
----------------
|
||||
Device: atmega328p
|
||||
|
||||
Program: 4702 bytes (14.3% Full)
|
||||
(.text + .data + .bootloader)
|
||||
|
||||
Data: 460 bytes (22.5% Full)
|
||||
(.data + .bss + .noinit)
|
||||
|
||||
|
||||
========================= [test::*] Uploading... (2/3) ==============================
|
||||
|
||||
[Wed Jun 15 00:27:43 2016] Processing uno (platform: atmelavr, board: uno, framework: arduino)
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
avr-g++ -o .pioenvs/uno/firmware.elf -Os -mmcu=atmega328p -Wl,--gc-sections,--relax .pioenvs/uno/src/main.o .pioenvs/uno/test/output_export.o .pioenvs/uno/test/test_main.o -L.pioenvs/uno -Wl,--start-group .pioenvs/uno/libUnityTestLib.a .pioenvs/uno/libFrameworkArduinoVariant.a .pioenvs/uno/libFrameworkArduino.a -lm -Wl,--end-group
|
||||
MethodWrapper([".pioenvs/uno/firmware.elf"], [".pioenvs/uno/src/main.o", ".pioenvs/uno/test/output_export.o", ".pioenvs/uno/test/test_main.o"])
|
||||
Check program size...
|
||||
text data bss dec hex filename
|
||||
4464 238 222 4924 133c .pioenvs/uno/firmware.elf
|
||||
BeforeUpload(["upload"], [".pioenvs/uno/firmware.hex"])
|
||||
Looking for upload port/disk...
|
||||
avr-size --mcu=atmega328p -C -d .pioenvs/uno/firmware.elf
|
||||
|
||||
Auto-detected: /dev/cu.usbmodemFD131
|
||||
avrdude -v -p atmega328p -C "/Users/ikravets/.platformio/packages/tool-avrdude/avrdude.conf" -c arduino -b 115200 -P "/dev/cu.usbmodemFD131" -D -U flash:w:.pioenvs/uno/firmware.hex:i
|
||||
|
||||
[...]
|
||||
|
||||
avrdude done. Thank you.
|
||||
|
||||
========================= [test::*] Testing... (3/3) =========================
|
||||
|
||||
If you do not see any output for the first 10 secs, please reset board (press reset button)
|
||||
|
||||
test/test_main.cpp:30:test_led_builtin_pin_number PASSED
|
||||
test/test_main.cpp:41:test_led_state_high PASSED
|
||||
test/test_main.cpp:43:test_led_state_low PASSED
|
||||
test/test_main.cpp:41:test_led_state_high PASSED
|
||||
test/test_main.cpp:43:test_led_state_low PASSED
|
||||
test/test_main.cpp:41:test_led_state_high PASSED
|
||||
test/test_main.cpp:43:test_led_state_low PASSED
|
||||
test/test_main.cpp:41:test_led_state_high PASSED
|
||||
test/test_main.cpp:43:test_led_state_low PASSED
|
||||
test/test_main.cpp:41:test_led_state_high PASSED
|
||||
test/test_main.cpp:43:test_led_state_low PASSED
|
||||
-----------------------
|
||||
11 Tests 0 Failures 0 Ignored
|
||||
|
||||
========================= [TEST SUMMARY] =====================================
|
||||
test:*/env:uno PASSED
|
||||
========================= [PASSED] Took 13.35 seconds ========================
|
@ -113,7 +113,7 @@ This option can be overridden by global environment variable
|
||||
:envvar:`PLATFORMIO_ENVS_DIR`.
|
||||
|
||||
.. note::
|
||||
If you have any problems with building your Project environmets which
|
||||
If you have any problems with building your Project environments which
|
||||
are defined in :ref:`projectconf`, then **TRY TO DELETE** this folder. In
|
||||
this situation you will remove all cached files without any risk.
|
||||
|
||||
@ -130,6 +130,19 @@ project.
|
||||
This option can be overridden by global environment variable
|
||||
:envvar:`PLATFORMIO_DATA_DIR`.
|
||||
|
||||
.. _projectconf_pio_test_dir:
|
||||
|
||||
``test_dir``
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Directory for :ref:`unit_testing`.
|
||||
|
||||
A default value is ``test`` which means that folder is located in the root of
|
||||
project.
|
||||
|
||||
This option can be overridden by global environment variable
|
||||
:envvar:`PLATFORMIO_TEST_DIR`.
|
||||
|
||||
.. _projectconf_pio_env_default:
|
||||
|
||||
``env_default``
|
||||
|
@ -82,11 +82,10 @@ According to the table above the ID/TYPE for Teensy 3.1 is ``teensy31``. Also,
|
||||
the ID for Arduino UNO is ``uno`` and for NodeMCU 1.0 (ESP-12E Module)
|
||||
is ``nodemcuv2``.
|
||||
|
||||
|
||||
Initialize Project
|
||||
------------------
|
||||
|
||||
PlatformIO ecosystem contains huge database with pre-configured settings for the
|
||||
PlatformIO ecosystem contains big database with pre-configured settings for the
|
||||
most popular embedded boards. It helps you to forget about installing
|
||||
toolchains, writing build scripts or configuring uploading process. Just tell
|
||||
PlatformIO the Board ID and you will receive full working project with
|
||||
|
@ -64,7 +64,9 @@ Pre-built targets:
|
||||
--upload-port
|
||||
|
||||
Upload port of embedded board. To print all available ports use
|
||||
:ref:`cmd_serialports` command
|
||||
:ref:`cmd_serialports` command.
|
||||
|
||||
If upload port is not specified, PlatformIO will try to detect it automatically.
|
||||
|
||||
.. option::
|
||||
-d, --project-dir
|
||||
|
68
docs/userguide/cmd_test.rst
Normal file
68
docs/userguide/cmd_test.rst
Normal file
@ -0,0 +1,68 @@
|
||||
.. Copyright 2014-2016 Ivan Kravets <me@ikravets.com>
|
||||
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.
|
||||
|
||||
.. _cmd_test:
|
||||
|
||||
platformio test
|
||||
===============
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
platformio test [OPTIONS]
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Run tests from PlatformIO based project. More details about PlatformIO
|
||||
:ref:`unit_testing`.
|
||||
|
||||
This command allows you to apply the tests for the environments specified
|
||||
in :ref:`projectconf`.
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
.. program:: platformio test
|
||||
|
||||
.. option::
|
||||
-e, --environment
|
||||
|
||||
Process specified environments. More details :option:`platformio run --environment`
|
||||
|
||||
.. option::
|
||||
--upload-port
|
||||
|
||||
Upload port of embedded board. To print all available ports use
|
||||
:ref:`cmd_serialports` command.
|
||||
|
||||
If upload port is not specified, PlatformIO will try to detect it automatically.
|
||||
|
||||
.. option::
|
||||
-d, --project-dir
|
||||
|
||||
Specify the path to project directory. By default, ``--project-dir`` is equal
|
||||
to current working directory (``CWD``).
|
||||
|
||||
.. option::
|
||||
-v, --verbose
|
||||
|
||||
Shows details about the results of processing environments. More details
|
||||
:option:`platformio run --verbose`
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
For the examples please follow to :ref:`unit_testing` page.
|
@ -66,5 +66,6 @@ Commands
|
||||
cmd_run
|
||||
cmd_serialports
|
||||
cmd_settings
|
||||
cmd_test
|
||||
cmd_update
|
||||
cmd_upgrade
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
import sys
|
||||
|
||||
VERSION = (3, 0, "0.dev0")
|
||||
VERSION = (3, 0, "0.dev1")
|
||||
__version__ = ".".join([str(s) for s in VERSION])
|
||||
|
||||
__title__ = "platformio"
|
||||
|
@ -32,6 +32,7 @@ commonvars.AddVariables(
|
||||
("BUILD_SCRIPT",),
|
||||
("EXTRA_SCRIPT",),
|
||||
("PIOENV",),
|
||||
("PIOTEST",),
|
||||
("PLATFORM",),
|
||||
|
||||
# options
|
||||
|
@ -16,10 +16,10 @@ from __future__ import absolute_import
|
||||
|
||||
import atexit
|
||||
from os import remove
|
||||
from os.path import isdir, isfile, join
|
||||
from os.path import isdir, isfile, join, sep
|
||||
from string import Template
|
||||
|
||||
FRAMEWORKS_PARAMETERS = {
|
||||
FRAMEWORK_PARAMETERS = {
|
||||
"arduino": {
|
||||
"framework": "Arduino.h",
|
||||
"serial_obj": "",
|
||||
@ -50,9 +50,6 @@ FRAMEWORKS_PARAMETERS = {
|
||||
|
||||
|
||||
def ProcessTest(env):
|
||||
|
||||
test_dir = env.subst("$PROJECTTEST_DIR")
|
||||
|
||||
env.Append(
|
||||
CPPDEFINES=[
|
||||
"UNIT_TEST",
|
||||
@ -63,19 +60,23 @@ def ProcessTest(env):
|
||||
join("$BUILD_DIR", "UnityTestLib")
|
||||
]
|
||||
)
|
||||
|
||||
unitylib = env.BuildLibrary(
|
||||
join("$BUILD_DIR", "UnityTestLib"),
|
||||
env.DevPlatform().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 = "+<output_export.cpp>"
|
||||
src_filter += " +<%s%s>" % (env['PIOTEST'], sep)
|
||||
|
||||
return env.LookupSources(
|
||||
"$BUILDTEST_DIR", test_dir, duplicate=False)
|
||||
"$BUILDTEST_DIR", test_dir, duplicate=False, src_filter=src_filter
|
||||
)
|
||||
|
||||
|
||||
def GenerateOutputReplacement(env, destination_dir):
|
||||
@ -122,12 +123,12 @@ void output_complete(void)
|
||||
"Please remove it manually." % file_)
|
||||
|
||||
framework = env.subst("$FRAMEWORK").lower()
|
||||
if framework not in FRAMEWORKS_PARAMETERS.keys():
|
||||
if framework not in FRAMEWORK_PARAMETERS.keys():
|
||||
env.Exit(
|
||||
"Error: %s framework doesn't support testing feature!" % framework)
|
||||
else:
|
||||
data = Template(TEMPLATECPP).substitute(
|
||||
FRAMEWORKS_PARAMETERS[framework])
|
||||
FRAMEWORK_PARAMETERS[framework])
|
||||
|
||||
tmp_file = join(destination_dir, "output_export.cpp")
|
||||
with open(tmp_file, "w") as f:
|
||||
|
@ -41,19 +41,8 @@ from platformio.managers.platform import PlatformFactory
|
||||
@click.pass_context
|
||||
def cli(ctx, environment, target, upload_port, # pylint: disable=R0913,R0914
|
||||
project_dir, verbose, disable_auto_clean):
|
||||
assert check_project_envs(project_dir, environment)
|
||||
with util.cd(project_dir):
|
||||
config = util.get_project_config()
|
||||
|
||||
if not config.sections():
|
||||
raise exception.ProjectEnvsNotAvailable()
|
||||
|
||||
known = set([s[4:] for s in config.sections()
|
||||
if s.startswith("env:")])
|
||||
unknown = set(environment) - known
|
||||
if unknown:
|
||||
raise exception.UnknownEnvNames(
|
||||
", ".join(unknown), ", ".join(known))
|
||||
|
||||
# clean obsolete .pioenvs dir
|
||||
if not disable_auto_clean:
|
||||
try:
|
||||
@ -66,6 +55,7 @@ def cli(ctx, environment, target, upload_port, # pylint: disable=R0913,R0914
|
||||
fg="yellow"
|
||||
)
|
||||
|
||||
config = util.get_project_config()
|
||||
env_default = None
|
||||
if config.has_option("platformio", "env_default"):
|
||||
env_default = [
|
||||
@ -94,6 +84,8 @@ def cli(ctx, environment, target, upload_port, # pylint: disable=R0913,R0914
|
||||
options = {}
|
||||
for k, v in config.items(section):
|
||||
options[k] = v
|
||||
if "piotest" not in options and "piotest" in ctx.meta:
|
||||
options['piotest'] = ctx.meta['piotest']
|
||||
|
||||
ep = EnvironmentProcessor(
|
||||
ctx, envname, options, target, upload_port, verbose)
|
||||
@ -136,15 +128,12 @@ class EnvironmentProcessor(object):
|
||||
result = self._run()
|
||||
|
||||
is_error = result['returncode'] != 0
|
||||
summary_text = " Took %.2f seconds " % (time() - start_time)
|
||||
half_line = "=" * ((terminal_width - len(summary_text) - 10) / 2)
|
||||
click.echo("%s [%s]%s%s" % (
|
||||
half_line,
|
||||
(click.style(" ERROR ", fg="red", bold=True)
|
||||
if is_error else click.style("SUCCESS", fg="green", bold=True)),
|
||||
summary_text,
|
||||
half_line
|
||||
), err=is_error)
|
||||
if is_error or "piotest_processor" not in self.cmd_ctx.meta:
|
||||
print_header("[%s] Took %.2f seconds" % (
|
||||
(click.style("ERROR", fg="red", bold=True) if is_error
|
||||
else click.style("SUCCESS", fg="green", bold=True)),
|
||||
time() - start_time
|
||||
), is_error=is_error)
|
||||
|
||||
return not is_error
|
||||
|
||||
@ -254,6 +243,29 @@ def _clean_pioenvs_dir(pioenvs_dir):
|
||||
f.write(proj_hash)
|
||||
|
||||
|
||||
def print_header(label, is_error=False):
|
||||
terminal_width, _ = click.get_terminal_size()
|
||||
width = len(click.unstyle(label))
|
||||
half_line = "=" * ((terminal_width - width - 2) / 2)
|
||||
click.echo("%s %s %s" % (half_line, label, half_line), err=is_error)
|
||||
|
||||
|
||||
def check_project_envs(project_dir, environments):
|
||||
with util.cd(project_dir):
|
||||
config = util.get_project_config()
|
||||
|
||||
if not config.sections():
|
||||
raise exception.ProjectEnvsNotAvailable()
|
||||
|
||||
known = set([s[4:] for s in config.sections()
|
||||
if s.startswith("env:")])
|
||||
unknown = set(environments) - known
|
||||
if unknown:
|
||||
raise exception.UnknownEnvNames(
|
||||
", ".join(unknown), ", ".join(known))
|
||||
return True
|
||||
|
||||
|
||||
def calculate_project_hash():
|
||||
structure = []
|
||||
for d in (util.get_projectsrc_dir(), util.get_projectlib_dir()):
|
||||
|
188
platformio/commands/test.py
Normal file
188
platformio/commands/test.py
Normal file
@ -0,0 +1,188 @@
|
||||
# Copyright 2014-present Ivan Kravets <me@ikravets.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
from os import getcwd, listdir
|
||||
from os.path import isdir, join
|
||||
from time import sleep, time
|
||||
|
||||
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
|
||||
|
||||
|
||||
@click.command("test", short_help="Unit Testing")
|
||||
@click.option("--environment", "-e", multiple=True, metavar="<environment>")
|
||||
@click.option("--upload-port", metavar="<upload port>")
|
||||
@click.option("--project-dir", "-d", default=getcwd,
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True,
|
||||
writable=True, resolve_path=True))
|
||||
@click.option("--verbose", "-v", count=True, default=3)
|
||||
@click.pass_context
|
||||
def cli(ctx, environment, upload_port, # pylint: disable=R0913,R0914
|
||||
project_dir, verbose):
|
||||
assert check_project_envs(project_dir, environment)
|
||||
with util.cd(project_dir):
|
||||
test_dir = util.get_projecttest_dir()
|
||||
if not isdir(test_dir):
|
||||
raise exception.TestDirEmpty(test_dir)
|
||||
config = util.get_project_config()
|
||||
env_names = set(
|
||||
[s[4:] for s in config.sections() if s.startswith("env:")])
|
||||
|
||||
test_names = []
|
||||
for item in sorted(listdir(test_dir)):
|
||||
if isdir(join(test_dir, item)):
|
||||
test_names.append(item)
|
||||
if not test_names:
|
||||
test_names = ["*"]
|
||||
click.echo("Collected %d items" % len(test_names))
|
||||
click.echo()
|
||||
|
||||
start_time = time()
|
||||
results = []
|
||||
for testname in test_names:
|
||||
for envname in env_names:
|
||||
if environment and envname not in environment:
|
||||
continue
|
||||
tp = TestProcessor(ctx, testname, envname, {
|
||||
"project_config": config,
|
||||
"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:
|
||||
if not result[0]:
|
||||
passed = False
|
||||
click.echo("test:%s/env:%s\t%s" % (
|
||||
click.style(result[1], fg="yellow"),
|
||||
click.style(result[2], fg="cyan"),
|
||||
click.style("PASSED" if passed else "FAILED", fg="green"
|
||||
if passed else "red")), err=not passed)
|
||||
|
||||
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 TestProcessor(object):
|
||||
|
||||
SERIAL_TIMEOUT = 600
|
||||
SERIAL_BAUDRATE = 9600
|
||||
|
||||
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
|
||||
|
||||
def process(self):
|
||||
self._progress("Building... (1/3)")
|
||||
self._build_or_upload(["test"])
|
||||
self._progress("Uploading... (2/3)")
|
||||
self._build_or_upload(["test", "upload"])
|
||||
self._progress("Testing... (3/3)")
|
||||
sleep(1.0) # wait while board is starting...
|
||||
return self._run_hardware_test()
|
||||
|
||||
def _progress(self, text, is_error=False):
|
||||
print_header("[test::%s] %s" % (
|
||||
click.style(self.test_name, fg="yellow", bold=True),
|
||||
text
|
||||
), is_error=is_error)
|
||||
click.echo()
|
||||
|
||||
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'],
|
||||
verbose=self.options['verbose'], environment=[self.env_name],
|
||||
target=target
|
||||
)
|
||||
|
||||
def _run_hardware_test(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)
|
||||
passed = True
|
||||
while True:
|
||||
line = ser.readline().strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.endswith(":PASS"):
|
||||
click.echo("%s\t%s" % (
|
||||
line[:-5], click.style("PASSED", fg="green")))
|
||||
elif ":FAIL:" in line:
|
||||
passed = False
|
||||
click.secho(line, fg="red")
|
||||
else:
|
||||
click.echo(line)
|
||||
if all([l in line for l in ("Tests", "Failures", "Ignored")]):
|
||||
break
|
||||
ser.close()
|
||||
return passed
|
||||
|
||||
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"))
|
||||
|
||||
platform = envdata['platform']
|
||||
version = None
|
||||
if "@" in platform:
|
||||
platform, version = platform.rsplit("@", 1)
|
||||
p = PlatformFactory.newPlatform(platform, version)
|
||||
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
|
@ -197,6 +197,13 @@ class CIBuildEnvsEmpty(PlatformioException):
|
||||
"predefined environments using `--project-conf` option"
|
||||
|
||||
|
||||
class TestDirEmpty(PlatformioException):
|
||||
|
||||
MESSAGE = "Test directory '{0}' is empty. More details about Unit "\
|
||||
"Testing:\n http://docs.platformio.org/en/latest/platforms/"\
|
||||
"unit_testing.html"
|
||||
|
||||
|
||||
class UpgradeError(PlatformioException):
|
||||
|
||||
MESSAGE = """{0}
|
||||
|
Reference in New Issue
Block a user