Skip to content

Commit

Permalink
introduce test_e2e
Browse files Browse the repository at this point in the history
The goal is to test the AYAB firmware at a higher level by looking at the Arduino interactions instead of mocking parts of the firmware.
  • Loading branch information
jonathanperret committed Oct 14, 2024
1 parent 85d0842 commit 885aa6a
Show file tree
Hide file tree
Showing 18 changed files with 1,411 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
if: always()
with:
files: |
./test/build/xml_out/*
./test/build/xml_out/**/*.xml
- name: Analyze with SonarCloud
if: github.repository == 'AllYarnsAreBeautiful/ayab-firmware' && ( ( github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'AllYarnsAreBeautiful/ayab-firmware' ) || github.event_name == 'push' )
env:
Expand Down
2 changes: 0 additions & 2 deletions src/ayab/global_knitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@ void GlobalKnitter::setUpInterrupt() {
m_instance->setUpInterrupt();
}

#ifndef AYAB_TESTS
void GlobalKnitter::isr() {
m_instance->isr();
}
#endif

Err_t GlobalKnitter::initMachine(Machine_t machine) {
return m_instance->initMachine(machine);
Expand Down
2 changes: 0 additions & 2 deletions src/ayab/global_tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ void GlobalTester::quitCmd() {
m_instance->quitCmd();
}

#ifndef AYAB_TESTS
void GlobalTester::encoderChange() {
m_instance->encoderChange();
}
#endif // AYAB_TESTS
3 changes: 1 addition & 2 deletions src/ayab/knitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,11 @@ void Knitter::init() {
void Knitter::setUpInterrupt() {
// (re-)attach ENC_PIN_A(=2), interrupt #0
detachInterrupt(digitalPinToInterrupt(ENC_PIN_A));
#ifndef AYAB_TESTS

// Attaching ENC_PIN_A, Interrupt #0
// This interrupt cannot be enabled until
// the machine type has been validated.
attachInterrupt(digitalPinToInterrupt(ENC_PIN_A), GlobalKnitter::isr, CHANGE);
#endif // AYAB_TESTS
}

/*!
Expand Down
2 changes: 0 additions & 2 deletions src/ayab/knitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ class GlobalKnitter final {

static void init();
static void setUpInterrupt();
#ifndef AYAB_TESTS
static void isr();
#endif
static Err_t startKnitting(uint8_t startNeedle,
uint8_t stopNeedle, uint8_t *pattern_start,
bool continuousReportingEnabled);
Expand Down
4 changes: 0 additions & 4 deletions src/ayab/solenoids.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ void Solenoids::setSolenoid(uint8_t solenoid, bool state) {
bitClear(solenoidState, solenoid);
}
if (oldState != solenoidState) {
#ifndef AYAB_TESTS
write(solenoidState);
#endif
}
}

Expand All @@ -73,9 +71,7 @@ void Solenoids::setSolenoid(uint8_t solenoid, bool state) {
void Solenoids::setSolenoids(uint16_t state) {
if (state != solenoidState) {
solenoidState = state;
#ifndef AYAB_TESTS
write(state);
#endif
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/ayab/tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,13 @@ void Tester::loop() {
}
}

#ifndef AYAB_TESTS
/*!
* \brief Interrupt service routine for encoder A.
*/
void Tester::encoderChange() {
digitalWrite(LED_PIN_A, digitalRead(ENC_PIN_A));
digitalWrite(LED_PIN_B, digitalRead(ENC_PIN_B));
}
#endif // AYAB_TESTS

// Private member functions

Expand All @@ -216,11 +214,9 @@ void Tester::setUp() {
GlobalCom::sendMsg(AYAB_API::testRes, buf);
helpCmd();

#ifndef AYAB_TESTS
// Attach interrupts for both encoder pins
attachInterrupt(digitalPinToInterrupt(ENC_PIN_A), GlobalTester::encoderChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENC_PIN_B), GlobalTester::encoderChange, CHANGE);
#endif // AYAB_TESTS

m_autoReadOn = false;
m_autoTestOn = false;
Expand Down
8 changes: 1 addition & 7 deletions src/ayab/tester.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ class TesterInterface {
virtual void autoTestCmd() = 0;
virtual void stopCmd() = 0;
virtual void quitCmd() = 0;
#ifndef AYAB_TESTS
virtual void encoderChange();
#endif
virtual void encoderChange() = 0;
};

// Container class for the static methods that implement the hardware test
Expand Down Expand Up @@ -83,9 +81,7 @@ class GlobalTester final {
static void autoTestCmd();
static void stopCmd();
static void quitCmd();
#ifndef AYAB_TESTS
static void encoderChange();
#endif
};

class Tester : public TesterInterface {
Expand All @@ -103,9 +99,7 @@ class Tester : public TesterInterface {
void autoTestCmd() final;
void stopCmd() final;
void quitCmd() final;
#ifndef AYAB_TESTS
void encoderChange() final;
#endif

private:
void setUp();
Expand Down
50 changes: 49 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ set(COMMON_LINKER_FLAGS
${ARDUINO_MOCK_LIBS_DIR}/dist/lib/libarduino_mock.a
${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest-build/lib/libgmock.a
${CMAKE_THREAD_LIBS_INIT}
-lgcov
--coverage
)
set(HARD_I2C_LIB
${LIBRARY_DIRECTORY}/Adafruit_MCP23008/Adafruit_MCP23008.cpp
Expand Down Expand Up @@ -166,7 +166,55 @@ target_link_libraries(${PROJECT_NAME}_knitter
)
add_dependencies(${PROJECT_NAME}_knitter arduino_mock)

add_executable(${PROJECT_NAME}_e2e
${PROJECT_SOURCE_DIR}/test_e2e.cpp
${PROJECT_SOURCE_DIR}/test_knitting_machine.cpp

${PROJECT_SOURCE_DIR}/mocks/knitting_machine.cpp

${SOURCE_DIRECTORY}/encoders.cpp
${SOURCE_DIRECTORY}/global_encoders.cpp

${SOURCE_DIRECTORY}/solenoids.cpp
${SOURCE_DIRECTORY}/global_solenoids.cpp
${HARD_I2C_LIB}

${SOURCE_DIRECTORY}/beeper.cpp
${SOURCE_DIRECTORY}/global_beeper.cpp

${SOURCE_DIRECTORY}/com.cpp
${SOURCE_DIRECTORY}/global_com.cpp

${SOURCE_DIRECTORY}/tester.cpp
${SOURCE_DIRECTORY}/global_tester.cpp

${SOURCE_DIRECTORY}/knitter.cpp
${SOURCE_DIRECTORY}/global_knitter.cpp

${SOURCE_DIRECTORY}/fsm.cpp
${SOURCE_DIRECTORY}/global_fsm.cpp
)
target_include_directories(${PROJECT_NAME}_e2e
PRIVATE
${COMMON_INCLUDES}
${EXTERNAL_LIB_INCLUDES}
)
target_compile_definitions(${PROJECT_NAME}_e2e
PRIVATE
${COMMON_DEFINES}
__AVR_ATmega328P__
)
target_compile_options(${PROJECT_NAME}_e2e PRIVATE
${COMMON_FLAGS}
-fno-inline
)
target_link_libraries(${PROJECT_NAME}_e2e
${COMMON_LINKER_FLAGS}
)
add_dependencies(${PROJECT_NAME}_e2e arduino_mock)

enable_testing()
include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME}_uno TEST_PREFIX uno_ XML_OUTPUT_DIR ./xml_out)
gtest_discover_tests(${PROJECT_NAME}_knitter TEST_PREFIX knitter_ XML_OUTPUT_DIR ./xml_out)
gtest_discover_tests(${PROJECT_NAME}_e2e TEST_PREFIX e2e_ XML_OUTPUT_DIR ./xml_out)
116 changes: 116 additions & 0 deletions test/mocks/io_expanders_mock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*!`
* \file io_expanders_mock.h
*
* This file is part of AYAB.
*
* AYAB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AYAB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AYAB. If not, see <http://www.gnu.org/licenses/>.
*
* Original Work Copyright 2013 Christian Obersteiner, Andreas Müller
* Modified Work Copyright 2020 Sturla Lange, Tom Price
* http://ayab-knitting.com
*/

#ifndef IO_EXPANDERS_MOCK_H_
#define IO_EXPANDERS_MOCK_H_

#include <Adafruit_MCP23008.h>
#include <gmock/gmock.h>

#include <array>

using namespace ::testing;

/*! \brief Simulate I2C I/O expanders
*
* This class connects to arduino-mock's I2C (Wire) support and
* intercepts writes to the simulated I2C bus, emulating a pair of
* MCP23008 I/O expanders.
*
* Of the MCP23008 protocol, only the bare minimum necessary to support
* GPIO writes is implemented.
*
* The current GPIO state can be retrieved at any time by calling
* gpioState(). The result is the state of the 16 digital outputs,
* in the order that they are normally connected to the knitting
* machine's solenoids, i.e. starting from the leftmost solenoid.
*
* Note that a KH270 machine only has 12 solenoids, but since this
* class simulates the I/O expanders, that always have 16 outputs,
* that fact is not relevant here.
*
* Creating an instance of this class takes over WireMock, no expectations
* should be setup on it from outside until that instance is destroyed
* and releaseWireMock is called.
*/
class IOExpandersMock {
public:
IOExpandersMock(WireMock *wireMock) {
EXPECT_CALL(*wireMock, beginTransmission(_))
.Times(AnyNumber())
.WillRepeatedly(Invoke(this, &IOExpandersMock::beginTransmission));
EXPECT_CALL(*wireMock, write(An<uint8_t>()))
.Times(AnyNumber())
.WillRepeatedly(Invoke(this, &IOExpandersMock::write));

// We're not interested in calls to the following methods, but they
// happen and if we don't set up expectations for them GoogleMock will
// emit warnings.
EXPECT_CALL(*wireMock, begin()).Times(AnyNumber());
EXPECT_CALL(*wireMock, endTransmission()).Times(AnyNumber());
EXPECT_CALL(*wireMock, requestFrom(_, _)).Times(AnyNumber());
EXPECT_CALL(*wireMock, read).Times(AnyNumber());
}

std::array<bool, 16> gpioState() {
std::array<bool, 16> result;
for (int i = 0; i < 8; i++) {
result[i] = lowByte & (1 << i);
result[i + 8] = highByte & (1 << i);
}
return result;
}

private:
uint8_t i2c_address = 0, i2c_byteIndex = 0, i2c_register = 0;
uint8_t highByte = 0, lowByte = 0;

void beginTransmission(uint8_t address) {
i2c_address = address;
i2c_byteIndex = i2c_register = 0;
}

uint8_t write(uint8_t data) {
switch (i2c_byteIndex++) {
case 0:
i2c_register = data;
break;
case 1:
switch (i2c_register) {
case MCP23008_GPIO:
switch (i2c_address & ~MCP23008_ADDRESS) {
case 0:
lowByte = data;
break;
case 1:
highByte = data;
break;
}
break;
}
}
return 0;
}
};

#endif // IO_EXPANDERS_MOCK_H_
Loading

0 comments on commit 885aa6a

Please sign in to comment.