Skip to content

Commit

Permalink
Josh/3 high level io api (#5)
Browse files Browse the repository at this point in the history
* Modified unit tests to move generate_random_bytes() into a common header
* Wrote non-compiling but otherwise correct test for slot.read_card()
* Wrote code to make test case compile but fail
It's a shame array equality comparison is unusable for this example because Catch prints out the whole of both arrays and this takes ages (both are 128KiB for whole card)
* Wrote failing test case for whole card writing
* Wrote failing test case for card block reading
Added stub methods for card block reading and writing
* Added failing test for card writing by block
* Add failing tests for card reading/writing by sector
Also stubbed the methods for doing the same
* Wrote minimal code to implement read memory card sector
Missing things:
- invalid sector number test
- invalid checksum test (how on earth can this be tested?)
* Validate card checksum when determining sector read success/failure
* Implement whole block reading using template recursion
This is needed if one wants to use subspans (good because much more intuitive to work with spans of subspans for data output) as we need const-size spans and that method is only available when constexpr
* Wrote recursive implementation for reading entire card
* Add separate docs test job to CI for PRs (so broken docs block PRs)
* Swap to my version of doxygen-action to test my tweak
The tweak to the action is intended to expose Doxygen's return status code to Github Actions to allow us to fail the build when Doxygen fails
* Implemented writing of card sectors
* Implemented writing cards by block and whole card
* Fix mistakes in docs
  • Loading branch information
saxbophone authored May 6, 2021
1 parent 60e927b commit 8f93d71
Show file tree
Hide file tree
Showing 8 changed files with 516 additions and 13 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ on:
types: [opened, synchronize]

jobs:
test-docs-build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Build Doxygen Docs
uses: saxbophone/doxygen-action@master

continuous-integration:
runs-on: ${{ matrix.os }}
env:
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ else()
set(WONDERCARD_SUBPROJECT ON)
endif()

project(wondercard VERSION 0.1.9 LANGUAGES CXX)
project(wondercard VERSION 0.2.0 LANGUAGES CXX)

find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
Expand Down
15 changes: 3 additions & 12 deletions tests/MemoryCard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,11 @@
#include <wondercard/common.hpp>
#include <wondercard/MemoryCard.hpp>

#include "test_helpers.hpp"

using namespace com::saxbophone::wondercard;

template<std::size_t SIZE>
static std::array<Byte, SIZE> generate_random_bytes() {
std::array<Byte, SIZE> data;
std::default_random_engine engine;
// N.B: Can't use uint8_t for type as generator doesn't support char types
std::uniform_int_distribution<std::uint16_t> prng(0x0000, 0x00FF);
for (auto& d : data) {
d = (Byte)prng(engine);
}
return data;
}
using namespace com::saxbophone::wondercard;
using namespace com::saxbophone::wondercard::PRIVATE::test_helpers;

SCENARIO("MemoryCard can be powered on when off and off when on") {
GIVEN("A MemoryCard that is powered off") {
Expand Down
179 changes: 179 additions & 0 deletions tests/MemoryCardSlot.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <array>
#include <optional>
#include <vector>

Expand All @@ -9,8 +10,11 @@
#include <wondercard/MemoryCard.hpp>
#include <wondercard/MemoryCardSlot.hpp>

#include "test_helpers.hpp"


using namespace com::saxbophone::wondercard;
using namespace com::saxbophone::wondercard::PRIVATE::test_helpers;

SCENARIO("MemoryCards can be inserted and removed from MemoryCardSlot") {
GIVEN("An empty MemoryCardSlot") {
Expand Down Expand Up @@ -99,3 +103,178 @@ SCENARIO("Calling MemoryCardSlot.send() with a MemoryCard inserted behaves same
}
}
}

SCENARIO("Using higher level I/O API to read entire MemoryCard") {
GIVEN("An entire MemoryCard's-worth of data") {
std::array<
Byte,
MemoryCard::CARD_SIZE
> data = generate_random_bytes<MemoryCard::CARD_SIZE>();
AND_GIVEN("A MemoryCard initialised with that data") {
MemoryCard card(data);
auto bytes = card.bytes;
// verify card data is correct --test is invalid if not
for (std::size_t i = 0; i < MemoryCard::CARD_SIZE; i++) {
REQUIRE(bytes[i] == data[i]);
}
AND_GIVEN("A MemoryCardSlot that the card is successfully inserted into") {
MemoryCardSlot slot;
REQUIRE(slot.insert_card(card));
THEN("Calling MemoryCardSlot.read_card() returns an array identical to the data") {
std::array<
Byte,
MemoryCard::CARD_SIZE
> card_data;
REQUIRE(slot.read_card(card_data));
for (std::size_t i = 0; i < MemoryCard::CARD_SIZE; i++) {
REQUIRE(card_data[i] == data[i]);
}
}
}
}
}
}

SCENARIO("Using higher level I/O API to write entire MemoryCard") {
GIVEN("An entire MemoryCard's-worth of data") {
std::array<
Byte,
MemoryCard::CARD_SIZE
> data = generate_random_bytes<MemoryCard::CARD_SIZE>();
AND_GIVEN("An empty MemoryCard") {
MemoryCard card;
AND_GIVEN("A MemoryCardSlot that the card is successfully inserted into") {
MemoryCardSlot slot;
REQUIRE(slot.insert_card(card));
WHEN("MemoryCardSlot.write_card() is called with the data") {
REQUIRE(slot.write_card(data));
THEN("The card data bytes should equal those of the data") {
auto bytes = card.bytes;
for (std::size_t i = 0; i < MemoryCard::CARD_SIZE; i++) {
REQUIRE(bytes[i] == data[i]);
}
}
}
}
}
}
}

SCENARIO("Using higher level I/O API to read a MemoryCard Block") {
GIVEN("A Block's-worth of random data") {
std::array<
Byte,
MemoryCard::BLOCK_SIZE
> data = generate_random_bytes<MemoryCard::BLOCK_SIZE>();
// generate valid block numbers to try reading
std::size_t block_number = GENERATE(range(0u, 15u));
AND_GIVEN("A MemoryCard with a specific block filled with the data") {
MemoryCard card;
auto block = card.get_block(block_number);
for (std::size_t i = 0; i < MemoryCard::BLOCK_SIZE; i++) {
block[i] = data[i];
}
AND_GIVEN("A MemoryCardSlot with the card inserted into it") {
MemoryCardSlot slot;
REQUIRE(slot.insert_card(card));
WHEN("MemoryCardSlot.read_block() is called with the block number") {
std::array<
Byte,
MemoryCard::BLOCK_SIZE
> block_data;
REQUIRE(slot.read_block(block_number, block_data));
THEN("The returned data is equal to the generated data") {
for (std::size_t i = 0; i < MemoryCard::BLOCK_SIZE; i++) {
REQUIRE(block_data[i] == data[i]);
}
}
}
}
}
}
}

SCENARIO("Using higher level I/O API to write a MemoryCard Block") {
GIVEN("A Block's-worth of random data") {
std::array<
Byte,
MemoryCard::BLOCK_SIZE
> data = generate_random_bytes<MemoryCard::BLOCK_SIZE>();
// generate valid block numbers to try reading
std::size_t block_number = GENERATE(range(0u, 15u));
AND_GIVEN("A blank MemoryCard") {
MemoryCard card;
AND_GIVEN("A MemoryCardSlot with the card inserted into it") {
MemoryCardSlot slot;
REQUIRE(slot.insert_card(card));
WHEN("MemoryCardSlot.write_block() is called with the block number and generated data") {
REQUIRE(slot.write_block(block_number, data));
THEN("The corresponding block of the card is equal to generated data") {
auto block = card.get_block(block_number);
for (std::size_t i = 0; i < MemoryCard::BLOCK_SIZE; i++) {
REQUIRE(block[i] == data[i]);
}
}
}
}
}
}
}

SCENARIO("Using higher level I/O API to read a MemoryCard Sector") {
GIVEN("A Sector's-worth of random data") {
std::array<
Byte,
MemoryCard::SECTOR_SIZE
> data = generate_random_bytes<MemoryCard::SECTOR_SIZE>();
// generate valid sector numbers to try reading
std::size_t sector_number = GENERATE(range(0x000u, 0x3FFu)); // 0..1023
AND_GIVEN("A MemoryCard with a specific sector filled with the data") {
MemoryCard card;
auto sector = card.get_sector(sector_number);
for (std::size_t i = 0; i < MemoryCard::SECTOR_SIZE; i++) {
sector[i] = data[i];
}
AND_GIVEN("A MemoryCardSlot with the card inserted into it") {
MemoryCardSlot slot;
REQUIRE(slot.insert_card(card));
WHEN("MemoryCardSlot.read_sector() is called with the sector number and a destination array") {
std::array<Byte, MemoryCard::SECTOR_SIZE> output;
REQUIRE(slot.read_sector(sector_number, output));
THEN("The output data is equal to the generated data") {
for (std::size_t i = 0; i < MemoryCard::SECTOR_SIZE; i++) {
REQUIRE(output[i] == data[i]);
}
}
}
}
}
}
}

SCENARIO("Using higher level I/O API to write a MemoryCard Sector") {
GIVEN("A Sector's-worth of random data") {
std::array<
Byte,
MemoryCard::SECTOR_SIZE
> data = generate_random_bytes<MemoryCard::SECTOR_SIZE>();
// generate valid sector numbers to try reading
std::size_t sector_number = GENERATE(range(0x000u, 0x3FFu)); // 0..1023
AND_GIVEN("A blank MemoryCard") {
MemoryCard card;
AND_GIVEN("A MemoryCardSlot with the card inserted into it") {
MemoryCardSlot slot;
REQUIRE(slot.insert_card(card));
WHEN("MemoryCardSlot.write_sector() is called with the sector number and generated data") {
REQUIRE(slot.write_sector(sector_number, data));
THEN("The corresponding sector of the card is equal to generated data") {
auto sector = card.get_sector(sector_number);
for (std::size_t i = 0; i < MemoryCard::SECTOR_SIZE; i++) {
REQUIRE(sector[i] == data[i]);
}
}
}
}
}
}
}
27 changes: 27 additions & 0 deletions tests/test_helpers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef COM_SAXBOPHONE_WONDERCARD_PRIVATE_TESTS_TEST_HELPERS_HPP
#define COM_SAXBOPHONE_WONDERCARD_PRIVATE_TESTS_TEST_HELPERS_HPP

#include <random>

#include <cstdint>

#include <wondercard/common.hpp>


using namespace com::saxbophone::wondercard;

namespace com::saxbophone::wondercard::PRIVATE::test_helpers {
template<std::size_t SIZE>
std::array<Byte, SIZE> generate_random_bytes() {
std::array<Byte, SIZE> data;
std::default_random_engine engine;
// N.B: Can't use uint8_t for type as generator doesn't support char types
std::uniform_int_distribution<std::uint16_t> prng(0x0000, 0x00FF);
for (auto& d : data) {
d = (Byte)prng(engine);
}
return data;
}
}

#endif // include guard
65 changes: 65 additions & 0 deletions wondercard/include/wondercard/MemoryCardSlot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,72 @@ namespace com::saxbophone::wondercard {
*/
bool remove_card();

/**
* @brief Reads the entire contents of the inserted card
* @returns true/false indicating read sucess/failure
* @param[out] data destination to write read data to
* @warning Not Implemented
*/
bool read_card(std::span<Byte, MemoryCard::CARD_SIZE> data);

/**
* @brief Writes data from the given span to the entire card
* @returns true/false indicating write sucess/failure
* @param data Data to write to the card
*/
bool write_card(std::span<Byte, MemoryCard::CARD_SIZE> data);

/**
* @brief Reads the specified block of the inserted card
* @returns true/false indicating read sucess/failure
* @param index Block to read from
* @param[out] data destination to write read data to
* @todo Change return type to an enum or introduce exception throwing
* so the variety of causes of failure can be determined by the caller.
*/
bool read_block(std::size_t index, MemoryCard::Block data);

/**
* @brief Writes data from the given span to the specified block of the
* inserted card.
* @returns true/false indicating write sucess/failure
* @param index Block to write to
* @param data Data to write to the block
*/
bool write_block(std::size_t index, MemoryCard::Block data);

/**
* @brief Reads the specified sector of the inserted card
* @returns true/false indicating read sucess/failure
* @param index Sector to read from
* @param[out] data destination to write read data to
* @todo Change return type to an enum or introduce exception throwing
* so the variety of causes of failure can be determined by the caller.
*/
bool read_sector(std::size_t index, MemoryCard::Sector data);

/**
* @brief Writes data from the given span to the specified sector of
* the inserted card.
* @returns true/false indicating write sucess/failure
* @param index Sector to write to
* @param data Data to write to the sector
*/
bool write_sector(std::size_t index, MemoryCard::Sector data);

private:
template <std::size_t sector_index>
bool _read_block_sector(std::size_t block_sector, MemoryCard::Block data);

template <std::size_t sector_index>
bool _write_block_sector(std::size_t block_sector, MemoryCard::Block data);

template <std::size_t block_index>
bool _read_card_block(std::span<Byte, MemoryCard::CARD_SIZE> data);

template <std::size_t block_index>
bool _write_card_block(std::span<Byte, MemoryCard::CARD_SIZE> data);

MemoryCard* _inserted_card;
};
}
Expand Down
1 change: 1 addition & 0 deletions wondercard/src/MemoryCard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ namespace com::saxbophone::wondercard {
} else { // Good
data = 0x47;
}
this->_state = MemoryCard::State::IDLE;
return false;
}
return true;
Expand Down
Loading

0 comments on commit 8f93d71

Please sign in to comment.