Commit a5751081 authored by Thomas Corbat's avatar Thomas Corbat
Browse files

update w14

parent b8a67553
No preview for this file type
# No Week 14 in FS20!
# Exercise 14 - Build Systems
In this exercise...
* ... you will set up CMake to be used on your system
* ... write a `Makefile` for your `BoundedQueue`
* ... create a *CMake* setup for your `BoundedQueue`
* ... adjust your project layout to follow the best-practices shown in the lecture
# 1. Setting up CMake
Depending on your OS, install *CMake* on your system:
## Installation on Windows
Download the *CMake* installer from the [CMake website](https://cmake.org/download/). During installation, make sure to select the option to add *CMake* to your PATH!
## Installation on macOS
You should be able to install *CMake* via Homebrew.
## Installation on Linux
Install *CMake* using your distribution's package manger.
## Testing your installation
After installing *CMake*, test that your setup worked using the `w14_03_my_app_cmake` project from the `lecture_templates` folder:
1. Using your terminal, change the working directory to your copy of the example.
2. Create a new directory called `build` inside the example directory
3. Enter this new directory.
4. Invoke `cmake ..` to create a new set of build scripts for the project. ***NOTE:*** On Windows, you will have to add the command line parameter `-G"MinGW Makefiles"` to ensure *CMake* picks up the correct compiler!
5. Invoke `cmake --build .` to build the project.
If everything worked successfully, you should be left with an application called `my_app` (or `my_app.exe` if you are on Windows) that should produce the message "This is my awesome app!" when run.
# 2. A Makefile for `BoundedQueue`
The directory `w12_template_02_BoundedQueue` contains a flattened version of the test suite you used when building your `BoundedQueue`. Your task is to write a Makefile to build an executable called `BoundedQueueTests` from the provided sources. Use an approach similar to the one shown in the lecture when we discussed *Pattern Rules*. Specifically:
* Use a variable called `OBJECTS` containing all the object files you need to produce the executable
* Use a pattern rule to generalize the compiler invocation required.
* Remember:
* We are using C++17 features as well as threading. Make sure your compiler invocations use the correct flags!
* The CUTE headers are in the subdirectory `cute`. Make sure to instruct your compiler to look for headers files in this directory!
Afterwards, build your project using `make`.
## 2++ (Optional/Advanced)
Specifying the list object files manually is rather cumbersome. Can you figure out a way to have `make` find the sources/determine the object files for you? (Hint: the `make` functions `wildcard` and `patsubst` are your friends!)
# 3 CMake for `BoundedQueue`
Use the same sources as in the previous exercise, but this time around, your goal is to provide a *CMake* configuration for your project. Create a file called `CMakeLists.txt` in the root directory of your source code containing a configuration that covers the following points:
* Make sure that C++ is the only enabled language for your project.
* Again, we are using C++17. Set the correct *CMake* variable to the appropriate value
* We want to use standard C++. Make sure that non-standard extensions are disabled
* Remember to tell *CMake* that the `cute` directory contains header files required by our source code.
* Add an executable called `BoundedQueueTests` that is build using all `.cpp` files in the project.
* Ensure that your executable is linked against the `pthread` library.
Build your project using the commands discussed in the lecture.
## 3++ (Optional)
In the lecture, you have seen that *CMake* also ships with *CTest*. Extend your `CMakeLists.txt` so that you can use the `ctest` command to run your tests.
# 4 Project Structure
Having all files in a single directory gets messy fast. Adjust the directory hierarchy of your project according the guidelines presented in the lecture. Here are some things to keep in mind:
* You will need to adjust your `CMakeLists.txt` accordingly to reflect the new paths your resources live at.
* Headers, sources and third party libraries only used for tests should live in the according subdirectories inside the `test` folder.
Make sure your project still builds successfully.
Use the [forum](https://moodle.hsr.ch/mod/forum/view.php?id=151989) on Moodle for questions.
\ No newline at end of file
BoundedBuffer.h
BoundedQueue.h
*.o
BoundedQueueTests
BoundedQueueTests.xml
OBJECTS = bounded_queue_heap_memory_suite.o bounded_queue_student_suite.o bounded_queue_single_threaded_lock_suite.o bounded_queue_semantic_suite.o bounded_queue_multi_threaded_suite.o bounded_queue_default_behavior_suite.o Test.o bounded_queue_signatures_suite.o bounded_queue_content_suite.o bounded_queue_non_default_constructible_element_type_suite.o
all: BoundedQueueTests
BoundedQueueTests: $(OBJECTS)
g++ -o BoundedQueueTests $^ -lpthread
%.o: %.cpp
g++ -Icute -std=c++17 -c $<
SOURCES = $(wildcard *.cpp)
OBJECTS = $(patsubst %.cpp,%.o,$(SOURCES))
all: BoundedQueueTests
BoundedQueueTests: $(OBJECTS)
g++ -o BoundedQueueTests $^ -lpthread
%.o: %.cpp
g++ -Icute -std=c++17 -c $<
#ifndef MEMORYOPERATIONCOUNTER_H_
#define MEMORYOPERATIONCOUNTER_H_
struct MemoryOperationCounter {
MemoryOperationCounter() = default;
MemoryOperationCounter(unsigned moves, unsigned copies, bool known_state) :
moves { moves }, copies { copies }, known_state { known_state } {
}
MemoryOperationCounter(MemoryOperationCounter && source) noexcept :
moves { std::move(source.moves) }, copies { std::move(source.copies) }, known_state { std::move(source.known_state) } {
moves++;
source.known_state = false;
}
MemoryOperationCounter & operator=(MemoryOperationCounter && source) & noexcept {
copyFrom(source);
moves++;
source.known_state = false;
return *this;
}
MemoryOperationCounter(MemoryOperationCounter const & source) :
moves { std::move(source.moves) }, copies { std::move(source.copies) }, known_state { std::move(source.known_state) } {
copies++;
}
MemoryOperationCounter & operator=(MemoryOperationCounter const & source) & noexcept {
copyFrom(source);
copies++;
return *this;
}
bool operator==(MemoryOperationCounter const & other) const {
return (moves == other.moves) && (copies == other.copies) == (known_state == other.known_state);
}
void print(std::ostream & os) const {
os << "MemoryOperationsCounter{moves: " << moves << ", copies: " << copies << ", known_state: " << known_state << "}\n";
}
private:
unsigned moves { 0 };
unsigned copies { 0 };
bool known_state { true };
void copyFrom(MemoryOperationCounter const & source) {
moves = source.moves;
copies = source.copies;
known_state = source.known_state;
}
};
inline std::ostream & operator <<(std::ostream & os, MemoryOperationCounter const & counter) {
counter.print(os);
return os;
}
#endif /* MEMORYOPERATIONCOUNTER_H_ */
#include "cute.h"
#include "ide_listener.h"
#include "xml_listener.h"
#include "cute_runner.h"
#include "bounded_queue_signatures_suite.h"
#include "bounded_queue_default_behavior_suite.h"
#include "bounded_queue_content_suite.h"
#include "bounded_queue_semantic_suite.h"
#include "bounded_queue_student_suite.h"
#include "bounded_queue_heap_memory_suite.h"
#include "bounded_queue_non_default_constructible_element_type_suite.h"
#include "bounded_queue_single_threaded_lock_suite.h"
#include "bounded_queue_multi_threaded_suite.h"
bool runAllTests(int argc, char const *argv[]){
//TODO add your test here
cute::xml_file_opener xmlfile(argc,argv);
cute::xml_listener<cute::ide_listener<> > lis(xmlfile.out);
bool success = true;
success &= cute::makeRunner(lis,argc,argv)(make_suite_bounded_queue_signatures_suite(), "BoundedQueue API Tests");
success &= cute::makeRunner(lis,argc,argv)(make_suite_bounded_queue_default_behavior_suite(), "BoundedQueue Default Behavior Tests");
success &= cute::makeRunner(lis,argc,argv)(make_suite_bounded_queue_content_suite(), "BoundedQueue Content Tests");
success &= cute::makeRunner(lis,argc,argv)(make_suite_bounded_queue_semantic_suite(), "BoundedQueue Semantic Tests");
success &= cute::makeRunner(lis,argc,argv)(make_suite_bounded_queue_student_suite(), "BoundedQueue Student Tests");
success &= cute::makeRunner(lis,argc,argv)(make_suite_bounded_queue_heap_memory_suite(), "BoundedQueue Heap Memory Tests");
success &= cute::makeRunner(lis,argc,argv)(make_suite_bounded_queue_non_default_constructible_element_type_suite(), "BoundedQueue Non-Default-Constructible Element Type Tests");
success &= cute::makeRunner(lis,argc,argv)(make_suite_bounded_queue_single_threaded_lock_suite(), "BoundedQueue Single Threaded Lock Tests");
success &= cute::makeRunner(lis,argc,argv)(make_suite_bounded_queue_multi_threaded_suite(), "BoundedQueue Multi-Threaded Tests");
return success;
}
int main(int argc, char const *argv[]){
return !runAllTests(argc,argv);
}
#include "bounded_queue_content_suite.h"
#include "cute.h"
#include "BoundedQueue.h"
#include "times_literal.hpp"
#include <stdexcept>
using namespace times::literal;
void test_queue_is_not_empty_after_push_rvalue() {
BoundedQueue<int> queue { 5 };
queue.push(5);
ASSERTM("Queue should not be empty after push", !queue.empty());
}
void test_queue_is_not_empty_after_push_lvalue() {
BoundedQueue<int> queue { 5 };
int const lvalue { 5 };
queue.push(lvalue);
ASSERTM("Queue should not be empty after push", !queue.empty());
}
void test_queue_is_empty_after_one_push_and_pop() {
BoundedQueue<int> queue { 5 };
queue.push(5);
queue.pop();
ASSERTM("Queue should be empty after one push and pop", queue.empty());
}
void test_queue_of_size_two_is_full_after_two_pushs() {
BoundedQueue<int> queue { 2 };
2_times([&]() {queue.push(5);});
ASSERTM("Queue of size two should be full after two pushs", queue.full());
}
void test_queue_size_is_one_after_push() {
BoundedQueue<int> queue { 2 };
queue.push(1);
ASSERT_EQUAL(1, queue.size());
}
void test_queue_size_is_fifty_after_fifty_pushs() {
BoundedQueue<int> queue { 75 };
50_times([&]() {queue.push(5);});
ASSERT_EQUAL(50, queue.size());
}
void test_queue_pop_gets_first_element_of_pushs() {
BoundedQueue<int> queue { 5 };
queue.push(1);
queue.push(2);
ASSERT_EQUAL(1, queue.pop());
}
void test_queue_pop_in_sequence_of_push() {
BoundedQueue<int> queue { 5 };
std::vector<int> frontValues { }, expectedValues { 1, 2, 3, 4, 5 };
queue.push(1);
queue.push(2);
frontValues.push_back(queue.pop());
queue.push(3);
queue.push(4);
frontValues.push_back(queue.pop());
frontValues.push_back(queue.pop());
queue.push(5);
frontValues.push_back(queue.pop());
frontValues.push_back(queue.pop());
ASSERT_EQUAL(expectedValues, frontValues);
}
void test_queue_wrap_around_behavior_pop() {
BoundedQueue<int> queue { 3 };
std::vector<int> frontValues { }, expectedValues { 1, 2, 3, 4, 5, 6, 7 };
queue.push(1);
queue.push(2);
frontValues.push_back(queue.pop());
frontValues.push_back(queue.pop());
queue.push(3);
queue.push(4);
queue.push(5);
frontValues.push_back(queue.pop());
frontValues.push_back(queue.pop());
frontValues.push_back(queue.pop());
queue.push(6);
queue.push(7);
frontValues.push_back(queue.pop());
frontValues.push_back(queue.pop());
ASSERT_EQUAL(expectedValues, frontValues);
}
void test_queue_after_swap_this_has_argument_content() {
std::vector<int> frontValues { }, expectedValues { 1, 2, 3 };
BoundedQueue<int> queue { 5 }, otherQueue { 5 };
queue.push(1);
queue.push(2);
queue.push(3);
otherQueue.swap(queue);
3_times([&]() {frontValues.push_back(otherQueue.pop());});
ASSERT_EQUAL(expectedValues, frontValues);
}
void test_queue_after_swap_argument_has_this_content() {
std::vector<int> frontValues { }, expectedValues { 1, 2, 3 };
BoundedQueue<int> queue { 5 }, otherQueue { 5 };
queue.push(1);
queue.push(2);
queue.push(3);
queue.swap(otherQueue);
3_times([&]() {frontValues.push_back(otherQueue.pop()); });
ASSERT_EQUAL(expectedValues, frontValues);
}
cute::suite make_suite_bounded_queue_content_suite() {
cute::suite s;
s.push_back(CUTE(test_queue_is_not_empty_after_push_rvalue));
s.push_back(CUTE(test_queue_is_not_empty_after_push_lvalue));
s.push_back(CUTE(test_queue_is_empty_after_one_push_and_pop));
s.push_back(CUTE(test_queue_of_size_two_is_full_after_two_pushs));
s.push_back(CUTE(test_queue_size_is_one_after_push));
s.push_back(CUTE(test_queue_size_is_fifty_after_fifty_pushs));
s.push_back(CUTE(test_queue_pop_gets_first_element_of_pushs));
s.push_back(CUTE(test_queue_pop_in_sequence_of_push));
s.push_back(CUTE(test_queue_wrap_around_behavior_pop));
s.push_back(CUTE(test_queue_after_swap_this_has_argument_content));
s.push_back(CUTE(test_queue_after_swap_argument_has_this_content));
return s;
}
#ifndef BOUNDED_QUEUE_CONTENT_SUITE_H_
#define BOUNDED_QUEUE_CONTENT_SUITE_H_
#include "cute_suite.h"
extern cute::suite make_suite_bounded_queue_content_suite();
#endif
#include "bounded_queue_default_behavior_suite.h"
#include "cute.h"
#include "BoundedQueue.h"
#include <boost/type_index.hpp>
void test_queue_constructor_for_capacity_zero_throws() {
ASSERT_THROWS(BoundedQueue<int> queue{0}, std::invalid_argument);
}
void test_int_queue_of_capacity_thousand_is_empty() {
BoundedQueue<int> const queue{1000};
ASSERTM("New queue should be empty", queue.empty());
}
void test_const_int_queue_of_capacity_thousand_is_not_full() {
BoundedQueue<int> const queue{1000};
ASSERTM("New queue should not be full", !queue.full());
}
void test_int_queue_of_capacity_thousand_has_size_zero() {
BoundedQueue<int> const queue{1000};
ASSERT_EQUALM("New queue should be empty", 0, queue.size());
}
void test_empty_bounded_queue_returns_false_on_try_pop() {
BoundedQueue<int> queue{23};
int val{};
ASSERT(!queue.try_pop(val));
}
void test_full_bounded_queue_returns_false_on_try_push_const_lvalue() {
BoundedQueue<int> queue{1};
int const lvalue{23};
queue.push(lvalue);
ASSERT(!queue.try_push(lvalue));
}
void test_full_bounded_queue_returns_false_on_try_push_rvalue() {
BoundedQueue<int> queue{1};
int lvalue{23};
queue.push(lvalue);
ASSERT(!queue.try_push(std::move(lvalue)));
}
using namespace std::chrono_literals;
void test_empty_bounded_queue_returns_false_on_try_pop_for() {
BoundedQueue<int> queue{23};
int val{};
ASSERT(!queue.try_pop_for(val, 1ns));
}
void test_full_bounded_queue_returns_false_on_try_push_for_const_lvalue() {
BoundedQueue<int> queue{1};
int const lvalue{23};
queue.push(lvalue);
ASSERT(!queue.try_push_for(lvalue, 1ns));
}
cute::suite make_suite_bounded_queue_default_behavior_suite(){
cute::suite s{};
s.push_back(CUTE(test_int_queue_of_capacity_thousand_is_empty));
s.push_back(CUTE(test_queue_constructor_for_capacity_zero_throws));
s.push_back(CUTE(test_const_int_queue_of_capacity_thousand_is_not_full));
s.push_back(CUTE(test_int_queue_of_capacity_thousand_has_size_zero));
s.push_back(CUTE(test_empty_bounded_queue_returns_false_on_try_pop));
s.push_back(CUTE(test_full_bounded_queue_returns_false_on_try_push_const_lvalue));
s.push_back(CUTE(test_full_bounded_queue_returns_false_on_try_push_rvalue));
s.push_back(CUTE(test_empty_bounded_queue_returns_false_on_try_pop_for));
s.push_back(CUTE(test_full_bounded_queue_returns_false_on_try_push_for_const_lvalue));
return s;
}
#ifndef BOUNDED_QUEUE_DEFAULT_BEHAVIOR_SUITE_H_
#define BOUNDED_QUEUE_DEFAULT_BEHAVIOR_SUITE_H_
#include "cute_suite.h"
extern cute::suite make_suite_bounded_queue_default_behavior_suite();
#endif
#include "bounded_queue_heap_memory_suite.h"
#include "cute.h"
#include "BoundedQueue.h"
#include "times_literal.hpp"
#include <cstddef>
#include <ios>
#include <stdexcept>
#include <utility>
#include <vector>
struct AllocationTracker {
static void* operator new(std::size_t sz) {
AllocationTracker * ptr = static_cast<AllocationTracker*>(::operator new(sz));
allocatedSingleObjects.push_back(ptr);
return ptr;
}
static void* operator new[](std::size_t sz) {
AllocationTracker * ptr = static_cast<AllocationTracker*>(::operator new[](sz));
allocatedArrays.push_back(ptr);
return ptr;
}
static void operator delete(void* ptr) {
deallocatedSingleObjects.push_back(static_cast<AllocationTracker*>(ptr));
::operator delete(ptr);
}
static void operator delete[](void* ptr) {
deallocatedArrays.push_back(static_cast<AllocationTracker*>(ptr));
::operator delete[](ptr);
}
static void* operator new ( std::size_t count, void* ptr ) {
return ::operator new(count, ptr);
}
static std::vector<AllocationTracker*> allocatedSingleObjects;
static std::vector<AllocationTracker*> allocatedArrays;
static std::vector<AllocationTracker*> deallocatedSingleObjects;
static std::vector<AllocationTracker*> deallocatedArrays;
};
std::vector<AllocationTracker*> AllocationTracker::allocatedSingleObjects;
std::vector<AllocationTracker*> AllocationTracker::allocatedArrays;
std::vector<AllocationTracker*> AllocationTracker::deallocatedSingleObjects;
std::vector<AllocationTracker*> AllocationTracker::deallocatedArrays;
std::ostream & operator <<(std::ostream& out, AllocationTracker const * const ptr) {
out << "0x" << std::hex << (unsigned long long)ptr << std::dec;
return out;
}
void resetAllocationCounters() {
AllocationTracker::allocatedSingleObjects.clear();
AllocationTracker::allocatedArrays.clear();
AllocationTracker::deallocatedSingleObjects.clear();
AllocationTracker::deallocatedArrays.clear();
}
void test_allocation_of_default_bounded_queue() {
resetAllocationCounters();
{
BoundedQueue<AllocationTracker> queue { 2 };
}
ASSERT_EQUAL(0, AllocationTracker::allocatedArrays.size());
}
void test_deallocation_of_default_bounded_queue() {
resetAllocationCounters();
{
BoundedQueue<AllocationTracker> queue { 2 };
}
ASSERT_EQUAL(0, AllocationTracker::deallocatedArrays.size());
}
void test_no_undeleted_allocation_on_exception() {
resetAllocationCounters();
try {
BoundedQueue<AllocationTracker> queue { 0 };
FAILM("The tests expects the BoundedQueue not being constructible with size 0.");
} catch (std::invalid_argument const & e) {
ASSERT_EQUAL(AllocationTracker::deallocatedArrays, AllocationTracker::allocatedArrays);
}
}
void test_copy_constructor_allocates_a_new_queue() {
resetAllocationCounters();
BoundedQueue<AllocationTracker> queue { 15 };
BoundedQueue<AllocationTracker> copy { queue };
ASSERT_EQUAL(0, AllocationTracker::allocatedArrays.size());
}
void test_move_constructor_does_not_allocate_a_new_queue() {
resetAllocationCounters();
BoundedQueue<AllocationTracker> queue { 15 };
BoundedQueue<AllocationTracker> moved { std::move(queue) };
ASSERT_EQUAL(0, AllocationTracker::allocatedArrays.size());
}
void test_copy_assignment_one_additional_allocation() {
resetAllocationCounters();
BoundedQueue<AllocationTracker> queue { 3 }, copy { 2 };
queue.push(AllocationTracker{});
queue.push(AllocationTracker{});
copy = queue;
ASSERT_EQUAL(0, AllocationTracker::allocatedArrays.size());
}
void test_move_assignment_no_additional_allocation() {
resetAllocationCounters();
BoundedQueue<AllocationTracker> queue { 3 }, move { 2 };
queue.push(AllocationTracker{});
queue.push(AllocationTracker{});
move = std::move(queue);
ASSERT_EQUAL(0, AllocationTracker::allocatedArrays.size());