Automated Build Tests

Fixes #34
This commit is contained in:
Robin Davies
2022-03-16 00:18:17 -04:00
parent 8b222cbf0b
commit 2b2e82730c
13 changed files with 307 additions and 70 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"request": "launch", "request": "launch",
// Resolved by CMake Tools: // Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}", "program": "${command:cmake.launchTargetPath}",
"args": [ "[bluetooth_service]" ], "args": [ "[Dev]" ],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"environment": [ "environment": [
View File
+27 -14
View File
@@ -19,25 +19,38 @@
#include "pch.h" #include "pch.h"
#include "catch.hpp" #include "catch.hpp"
#include "BeastServer.hpp" #include "BeastServer.hpp"
#include "MemDebug.hpp"
#include <iostream>
using namespace pipedal; using namespace pipedal;
TEST_CASE( "BeastServer shutdown", "[beastServerShutdown]" ) { TEST_CASE("BeastServer shutdown", "[beastServerShutdown][Build][Dev]")
{
MemStats initialMemory = GetMemStats();
{
auto const address = boost::asio::ip::make_address("0.0.0.0");
auto const port = 8081;
std::string doc_root = ".";
auto const threads = 3;
auto const address = boost::asio::ip::make_address("0.0.0.0"); auto server = createBeastServer(
auto const port = 8081; address, port, doc_root.c_str(), threads);
std::string doc_root = "."; server->RunInBackground();
auto const threads = 3; sleep(5);
server->ShutDown(1000);
sleep(1);
server->Join();
}
MemStats finalMemory = GetMemStats();
auto server = createBeastServer( // currently leaking one allocation of 8 bytes. Acceptable.
address,port,doc_root.c_str(),threads); if (finalMemory.allocations > initialMemory.allocations+1)
server->RunInBackground(); {
sleep(30000); std::cout << "Leaked Allocations: " << (finalMemory.allocations- initialMemory.allocations) << std::endl;
server->ShutDown(1000); std::cout << "Leaked Memory: " << (finalMemory.allocated - initialMemory.allocated) << " bytes" << std::endl;
sleep(1);
server->Join(); REQUIRE(finalMemory.allocations == initialMemory.allocations);
}
} }
+8
View File
@@ -189,6 +189,7 @@ add_executable(pipedaltest ${PIPEDAL_SOURCES} testMain.cpp
SystemConfigFileTest.cpp SystemConfigFileTest.cpp
BeastServerTest.cpp BeastServerTest.cpp
MemDebug.cpp MemDebug.cpp
MemDebug.hpp
) )
configure_file(config.hpp.in config.hpp) configure_file(config.hpp.in config.hpp)
@@ -207,6 +208,13 @@ target_link_libraries(pipedaltest PRIVATE pthread atomic stdc++fs asound
${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} systemd SDBusCpp::sdbus-c++ ${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} systemd SDBusCpp::sdbus-c++
) )
# Build machine tests. Run tests that are not dependent on hardware.
add_test(NAME BuildTest COMMAND pipedaltest "[Build]")
# Developer tests. Run tests that only succeed on a Raspberry Pi with attached UBS Audio.
add_test(NAME DevTest COMMAND pipedaltest "[Dev]")
################################# #################################
+22 -3
View File
@@ -25,13 +25,32 @@
#include "Lv2Host.hpp" #include "Lv2Host.hpp"
#include "MemDebug.hpp"
using namespace pipedal; using namespace pipedal;
TEST_CASE( "Lv2Host memory leak", "[lv2host_leak]" ) { TEST_CASE( "Lv2Host memory leak", "[lv2host_leak][Build][Dev]" ) {
Lv2Host host; MemStats initialMemory = GetMemStats();
{
Lv2Host host;
host.Load("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2"); host.Load("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2");
}
MemStats finalMemory = GetMemStats();
// Something lilv leaks a Dublin Core url.
// Acceptable.
const int ACCEPTABLE_ALLOCATION_LEAKS = 4;
const int ACCEPTABLE_MEMORY_LEAK = 400;
if (finalMemory.allocations > initialMemory.allocations + ACCEPTABLE_ALLOCATION_LEAKS
|| finalMemory.allocated > initialMemory.allocated + ACCEPTABLE_MEMORY_LEAK)
{
std::cout << "Leaked Allocations: " << (finalMemory.allocations- initialMemory.allocations) << std::endl;
std::cout << "Leaked Memory: " << (finalMemory.allocated - initialMemory.allocated) << " bytes" << std::endl;
REQUIRE(finalMemory.allocations == initialMemory.allocations);
}
} }
+110 -31
View File
@@ -1,69 +1,148 @@
// Copyright (c) 2022 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "stdlib.h" #include "stdlib.h"
#include <mutex> #include <mutex>
#ifdef DEBUG #include "MemDebug.hpp"
#include <exception>
using namespace pipedal;
static std::mutex memMutex; static std::mutex memMutex;
struct MemStats {
size_t allocated = 0;
size_t allocations = 0; static MemStats memStats;
const MemStats&pipedal::GetMemStats()
{
return memStats;
}
const size_t MEM_GUARD = (size_t)0xBAADF00DBAADF00D;
struct MemHeader {
size_t size = 0;
MemHeader*pNext = nullptr;
MemHeader *pPrev = nullptr;
size_t mem_guard = MEM_GUARD;
}; };
MemStats memStats; static MemHeader memHead;
void *operator new(size_t size) void *operator new(size_t size)
{ {
char *p = (char*)malloc(size+32+4);
{ {
std::lock_guard lock(memMutex); std::lock_guard lock(memMutex);
MemHeader *pHead = (MemHeader*)p;
pHead->size = size;
pHead->pNext = nullptr;
pHead->pPrev = nullptr;
pHead->mem_guard = MEM_GUARD;
memStats.allocated += size; memStats.allocated += size;
memStats.allocations++; memStats.allocations++;
}
char *p = (char*)malloc(size+8); // link block into allocation chain
((size_t*)p)[0] = size; if (memHead.pNext == nullptr)
return p+8; {
memHead.pNext = memHead.pPrev = pHead;
pHead->pNext = pHead->pPrev = &memHead;
} else {
pHead->pNext = memHead.pNext;
pHead->pNext->pPrev = pHead;
pHead->pPrev = &memHead;
memHead.pNext = pHead;
}
p = p+32;
char *endGuard = p + size;
*endGuard++ = (char)0xBA;
*endGuard++ = (char)0xAD;
*endGuard++ = (char)0xF0;
*endGuard++ = (char)0x0D;
}
return p;
} }
static void bad_alloc();
void operator delete(void*mem) void operator delete(void*mem)
{ {
if (mem == nullptr) return;
char *p = ((char*)mem); char *p = ((char*)mem);
size_t size = *(size_t*)(p-8);
{ {
MemHeader *pHeader = (MemHeader*)(p-32);
std::lock_guard lock(memMutex); std::lock_guard lock(memMutex);
memStats.allocated -= size; memStats.allocated -= pHeader->size;
memStats.allocations--; memStats.allocations--;
// check the memory guards.
if (pHeader->mem_guard != MEM_GUARD)
{
throw std::bad_alloc();
}
char *pTail = p + pHeader->size;
if (*pTail++ != (char)0xBA) {
throw std::bad_alloc();
}
if (*pTail++ != (char)0xAD) {
throw std::bad_alloc();
}
if (*pTail++ != (char)0xF0) {
throw std::bad_alloc();
}
if (*pTail++ != (char)0x0D) {
throw std::bad_alloc();
}
// invalidate the memory guards (double free, eg).
pHeader->mem_guard = (size_t)0xDDDDDDDD;
pTail = p + pHeader->size;
*pTail++ = (char)0xDD;
*pTail++ = (char)0xDD;
*pTail++ = (char)0xDD;
*pTail++ = (char)0xDD;
// unlink the block from the allocation chain.
pHeader->pNext->pPrev = pHeader->pPrev;
pHeader->pPrev->pNext = pHeader->pNext;
pHeader->pNext = nullptr;
pHeader->pPrev = nullptr;
} }
free(p-32);
free(p-8);
} }
void* operator new[](size_t size) void* operator new[](size_t size)
{ {
{ return operator new(size);
std::lock_guard lock(memMutex);
memStats.allocated += size;
memStats.allocations++;
}
char *p = (char*)malloc(size+8);
((size_t*)p)[0] = size;
return p+8;
} }
void operator delete[](void*mem) void operator delete[](void*mem)
{ {
char *p = ((char*)mem); return operator delete(mem);
size_t size = *(size_t*)(p-8);
{
std::lock_guard lock(memMutex);
memStats.allocated -= size;
memStats.allocations--;
}
free(p-8);
} }
#endif
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) 2022 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <stddef.h>
#pragma once
namespace pipedal {
struct MemStats {
size_t allocated = 0;
size_t allocations = 0;
};
const MemStats &GetMemStats();
}
+3 -2
View File
@@ -22,16 +22,17 @@
#include <sstream> #include <sstream>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <iostream>
#include "PiPedalAlsa.hpp" #include "PiPedalAlsa.hpp"
using namespace pipedal; using namespace pipedal;
TEST_CASE( "ALSA Test", "[pipedal_alsa_test]" ) { TEST_CASE( "ALSA Test", "[pipedal_alsa_test][Build][Dev]" ) {
PiPedalAlsaDevices devices; PiPedalAlsaDevices devices;
auto result = devices.GetAlsaDevices(); auto result = devices.GetAlsaDevices();
REQUIRE(result.size() >= 1); std::cout << result.size() << " ALSA devices found." << 0;
} }
+6 -2
View File
@@ -44,7 +44,6 @@ static std::string makeLine(const std::string &key, const std::string &value)
void SystemConfigFile::Load(const std::filesystem::path &path) void SystemConfigFile::Load(const std::filesystem::path &path)
{ {
this->lines.clear();
ifstream f(path); ifstream f(path);
if (!f.is_open()) if (!f.is_open())
@@ -53,6 +52,12 @@ void SystemConfigFile::Load(const std::filesystem::path &path)
s << "File not found: " << path; s << "File not found: " << path;
throw PiPedalException(s.str()); throw PiPedalException(s.str());
} }
Load(f);
this->currentPath = path;
}
void SystemConfigFile::Load(std::istream&f) {
this->lines.clear();
while (true) while (true)
{ {
@@ -64,7 +69,6 @@ void SystemConfigFile::Load(const std::filesystem::path &path)
} }
lines.push_back(line); lines.push_back(line);
} }
this->currentPath = path;
} }
int64_t SystemConfigFile::GetLine(const std::string &key) const int64_t SystemConfigFile::GetLine(const std::string &key) const
+1
View File
@@ -36,6 +36,7 @@ public:
Load(path); Load(path);
} }
void Load(std::istream &input);
void Load(const std::filesystem::path&path); void Load(const std::filesystem::path&path);
void Save(std::ostream &os); void Save(std::ostream &os);
+78 -14
View File
@@ -25,30 +25,94 @@
#include "PiPedalException.hpp" #include "PiPedalException.hpp"
#include <iostream> #include <iostream>
#include "SystemConfigFile.hpp" #include "SystemConfigFile.hpp"
using namespace pipedal; using namespace pipedal;
static std::string GetTestFile()
{
std::stringstream s;
s
<< "interface=wlan0" << std::endl
<< "driver=nl80211" << std::endl
<< "country_code=CA" << std::endl
<< "" << std::endl
<< "# Wi-Fi features" << std::endl
<< "ieee80211n=1" << std::endl
<< "ieee80211d=1" << std::endl
<< "ieee80211ac=1" << std::endl
<< "ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]" << std::endl
<< "" << std::endl
<< "# Authentication options" << std::endl
<< "wmm_enabled=1" << std::endl
<< "auth_algs=1" << std::endl
<< "wpa=2" << std::endl
<< "wpa_pairwise=TKIP CCMP" << std::endl
<< "wpa_ptk_rekey=600" << std::endl
<< "rsn_pairwise=CCMP" << std::endl
<< "" << std::endl
<< "# Access point configuration" << std::endl
<< "ssid=pipedal" << std::endl
<< "hw_mode=g" << std::endl
<< "channel=8" << std::endl
<< "wpa_passphrase=PASSWORD" << std::endl;
TEST_CASE( "SystemConfigFile Test", "[system_config_file_test]" ) { return s.str();
std::filesystem::path path("/etc/hostapd/hostapd.conf"); }
static std::string GetExpectedFile()
{
std::stringstream s;
s
<< "interface=wlan0" << std::endl
<< "driver=nl80211" << std::endl
<< "country_code=FR" << std::endl
<< "" << std::endl
<< "# Wi-Fi features" << std::endl
<< "ieee80211n=1" << std::endl
<< "ieee80211d=1" << std::endl
<< "ieee80211ac=1" << std::endl
<< "ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]" << std::endl
<< "" << std::endl
<< "# Authentication options" << std::endl
<< "wmm_enabled=1" << std::endl
<< "auth_algs=1" << std::endl
<< "wpa=2" << std::endl
<< "wpa_pairwise=TKIP CCMP" << std::endl
<< "wpa_ptk_rekey=600" << std::endl
<< "rsn_pairwise=CCMP" << std::endl
<< "" << std::endl
<< "# Access point configuration" << std::endl
<< "ssid=newSsid" << std::endl
<< "hw_mode=g" << std::endl
<< "channel=8" << std::endl
<< "wpa_passphrase=PASSWORD" << std::endl
<< "" << std::endl
<< "# A new feature." << std::endl
<< "xx=new_feature" << std::endl;
return s.str();
}
SystemConfigFile file(path); TEST_CASE("SystemConfigFile Test", "[system_config_file_test]")
std::string driverName; {
std::stringstream testFile(GetTestFile());
SystemConfigFile file;
file.Load(testFile);
REQUIRE(file.Get("driver") == "nl80211"); REQUIRE(file.Get("driver") == "nl80211");
file.Set("ssid","ssid","Name of the access point"); file.Set("ssid", "ssid", "Name of the access point");
file.Set("xx","new_feature","A new feature."); file.Set("xx", "new_feature", "A new feature.");
file.Set("ssid","newSsid"); file.Set("ssid", "newSsid");
file.Set("country_code","FR"); file.Set("country_code", "FR");
file.Save(std::cout);
std::stringstream outputFile;
file.Save(outputFile);
std::string stringOutput = outputFile.str();
std::cout << stringOutput;
std::string expected = GetExpectedFile();
REQUIRE(stringOutput == expected);
} }
+17 -2
View File
@@ -162,10 +162,17 @@ private:
std::vector<json_member_reference_base<CLASS>* > members_; std::vector<json_member_reference_base<CLASS>* > members_;
json_map_impl(const json_map_impl&) { } // disable copy constructor. json_map_impl(const json_map_impl&) { } // disable copy constructor.
public: public:
json_map_impl(std::vector<json_member_reference_base<CLASS>*> members) using members_t = std::vector<json_member_reference_base<CLASS>*>;
json_map_impl(const members_t &members)
: members_(members) : members_(members)
{ {
}
json_map_impl(members_t &&members)
: members_(std::forward<members_t>(members))
{
} }
virtual ~json_map_impl() virtual ~json_map_impl()
{ {
@@ -185,12 +192,20 @@ public:
class json_map { class json_map {
public: public:
template <typename CLASS> class storage_type : public json_map_impl<CLASS> { template <typename CLASS> class storage_type : public json_map_impl<CLASS> {
public: public:
storage_type(std::vector<json_member_reference_base<CLASS>*> members)
using members_t = std::vector<json_member_reference_base<CLASS>*>;
storage_type(const members_t &members)
: json_map_impl<CLASS>(members) : json_map_impl<CLASS>(members)
{ {
} }
storage_type(members_t &&members)
: json_map_impl<CLASS>(std::forward<members_t>(members))
{
}
}; };
template <typename CLASS, typename MEMBER_TYPE> template <typename CLASS, typename MEMBER_TYPE>
static json_member_reference<CLASS,MEMBER_TYPE> * static json_member_reference<CLASS,MEMBER_TYPE> *
+1 -1
View File
@@ -149,7 +149,7 @@ TEST_CASE( "json read", "[json_read_test]" ) {
} }
TEST_CASE( "json smart ptrs", "[json_smart_ptrs]" ) { TEST_CASE( "json smart ptrs", "[json_smart_ptrs][Build][Dev]" ) {
std::string json =get_json(); std::string json =get_json();