diff --git a/ci_cd/.gitlab-ci.yml b/ci_cd/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..24774c79151e299b92d6217dd0c27522119399de --- /dev/null +++ b/ci_cd/.gitlab-ci.yml @@ -0,0 +1,11 @@ +stages: + - prebuild + - compile + - test + +include: + - local: 'ci_cd/problem2.yml' + - local: 'ci_cd/problem3.yml' + +default: + timeout: 5m diff --git a/ci_cd/problem2.yml b/ci_cd/problem2.yml new file mode 100644 index 0000000000000000000000000000000000000000..62d4ca91578a45568a769ec78f32ec7c784400e8 --- /dev/null +++ b/ci_cd/problem2.yml @@ -0,0 +1,52 @@ +prebuild_problem_2: + stage: prebuild + script: + - | + # Check if source files exist + if [ ! -f "impl/MyBloom.h" ]; then + echo "MyBloom.h does not exist under include directory"; + exit 1; + fi + if [ ! -f "impl/MyBloom.cpp" ]; then + echo "MyBloom.cpp does not exist under impl directory"; + exit 1; + fi + - git clone https://agile.bu.edu/gitlab/configs/ec330/homeworks/homeworkFour.git hw4 + artifacts: + paths: + - hw4/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem2"' + tags: [c++-17] + +compile_problem_2: + stage: compile + needs: + - job: prebuild_problem_2 + artifacts: true + script: + - cp include/MyBloom.h hw4/tests/include/ + - cp impl/MyBloom.cpp hw4/tests/impl/ + - cd hw4/tests + - make problem2 + artifacts: + paths: + - hw4/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem2"' + tags: [c++-17] + +exec_problem_2: + stage: test + needs: + - job: compile_problem_2 + artifacts: true + script: + - cd hw4/tests + - ./problem2 + artifacts: + paths: + - hw4/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem2"' + tags: [c++-17] \ No newline at end of file diff --git a/ci_cd/problem3.yml b/ci_cd/problem3.yml new file mode 100644 index 0000000000000000000000000000000000000000..152030e09bef8943a65499b3a87483a1bf158b2a --- /dev/null +++ b/ci_cd/problem3.yml @@ -0,0 +1,47 @@ +prebuild_problem_3: + stage: prebuild + script: + - | + # Check if source files exist + if [ ! -f "impl/MiningHash.cpp" ]; then + echo "MiningHash.cpp does not exist under impl directory"; + exit 1; + fi + - git clone https://agile.bu.edu/gitlab/configs/ec330/homeworks/homeworkFour.git hw4 + artifacts: + paths: + - hw4/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem3"' + tags: [c++-17] + +compile_problem_3: + stage: compile + needs: + - job: prebuild_problem_3 + artifacts: true + script: + - cp impl/MiningHash.cpp hw4/tests/impl/ + - cd hw4/tests + - make problem3 + artifacts: + paths: + - hw4/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem3"' + tags: [c++-17] + +exec_problem_3: + stage: test + needs: + - job: compile_problem_3 + artifacts: true + script: + - cd hw4/tests + - ./problem3 + artifacts: + paths: + - hw4/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem3"' + tags: [c++-17] \ No newline at end of file diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..c0d367b3ee3703972edd4119eb53f8671c21b37b --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,39 @@ +# Makefile, generated with support of ChatGPT + +# Compiler and flags +CXX = g++ -O2 +CXXFLAGS = -Wall -std=c++17 + +# Source files +PROBLEM2_SRCS = testBloom.cpp +PROBLEM3_SRCS = testHash.cpp +SRCS = $(PROBLEM2_SRCS) $(PROBLEM3_SRCS) + +# Object files +PROBLEM2_OBJS = testBloom.o +PROBLEM3_OBJS = testHash.o +OBJS = $(PROBLEM2_OBJS) $(PROBLEM3_OBJS) + +# Executable names +PROBLEM2_EXEC = problem2 +PROBLEM3_EXEC = problem3 +EXECS = $(PROBLEM2_EXEC) $(PROBLEM3_EXEC) + +# Default target to build the executable +all: $(PROBLEM2_EXEC) $(PROBLEM3_EXEC) + +# Rule to build the executable from object files +$(PROBLEM2_EXEC): $(PROBLEM2_OBJS) Makefile + $(CXX) $(CXXFLAGS) -o $(PROBLEM2_EXEC) $(PROBLEM2_OBJS) + +$(PROBLEM3_EXEC): $(PROBLEM3_OBJS) Makefile + $(CXX) $(CXXFLAGS) -o $(PROBLEM3_EXEC) $(PROBLEM3_OBJS) + +# Rules to build object files from source files, with dependency on the Common.h header +%.o: %.cpp Makefile + $(CXX) $(CXXFLAGS) -c $< -o $@ + +# Clean target to remove compiled files +clean: + rm -f $(OBJS) $(EXEC) + diff --git a/tests/include/Bloom.h b/tests/include/Bloom.h new file mode 100644 index 0000000000000000000000000000000000000000..6b55bbcfee920364a77c1a3c619045140b75c4c5 --- /dev/null +++ b/tests/include/Bloom.h @@ -0,0 +1,71 @@ +// +// Created by Ari Trachtenberg on 3/15/17. +// + +#ifndef BLOOM_OBJECT_H +#define BLOOM_OBJECT_H + +#include +#include +#include + +using namespace std; + +/** + * Represents a Bloom object corresponding to a character-based Bloom filter. + * \tparam length The length (in characters) of the Bloom filter. + */ +template +class Bloom { +public: + /** + * Instantiate an empty Bloom filter object + */ + Bloom () = default; + + /** + * @return A pointer to an empty Bloom filter. + */ + virtual unique_ptr< Bloom > makeBloom() = 0; + + /** + * Instantiates a Bloom filter object from a given \param theFilter string + * \param theFilter The object is created to represent this \param theFilter + * \pre \param theFilter must have been produced by the \ref getFilter() + * method of some BloomFilter object of the same length, or else the + * behavior is unspecified. + * \return A pointer to a Bloom filter corresponding to \param theFilter + */ + virtual unique_ptr< Bloom > makeBloom(const array& theFilter) = 0; + + /** + * inserts \param item into the Bloom object. + */ + virtual void insert(string item)=0; + + /** + * Checks whether \param item is in the Bloom filter object. + * @return true if \param item may be in the Bloom filter object + * false if \param item is definitely not in the Bloom filter object + */ + virtual bool exists(string item)=0; + + /** + * \return A filter string representing the Bloom object + */ + const array getFilter() const { return filter; } + + /** + * Destructor for the Bloom object + */ + virtual ~Bloom() = default; + +protected: + /** + * The filter itself. + */ + array filter; +}; + + +#endif //BLOOM_OBJECT_H diff --git a/tests/testBloom.cpp b/tests/testBloom.cpp new file mode 100644 index 0000000000000000000000000000000000000000..85f6b3ae92e233f108da875cb9da61d61cd66c64 --- /dev/null +++ b/tests/testBloom.cpp @@ -0,0 +1,238 @@ +// +// Created by Kevin on 3/9/2026. +// + +#include +#include +#include +#include + +#include "include/MyBloom.h" + +using namespace std; + +// helper function: consistent failure printing +bool failExample(const char* testName, + const std::string& msg, + const std::string& expected, + const std::string& got) { + std::cerr << "[" << testName << " FAILED] " << msg + << ": expected " << expected << ", got " << got << "\n"; + return false; +} + +// helper: compare two filters +template +static bool sameFilter(const Bloom& a, const Bloom& b) { + return a.getFilter() == b.getFilter(); +} + +// -------------------- Empty bloom should not contain a fresh item -------------------- +bool test_0() { + const char* T = "Empty bloom should not contain an arbitrary fresh item"; + + myBloom<16> bloom; + + // For an empty Bloom filter, exists should definitely be false + // because no bits have been set yet. + if (bloom.exists("apple")) { + return failExample( + T, + "empty bloom reported an item as present", + "false", + "true" + ); + } + + return true; +} + +// -------------------- Inserted item must exist -------------------- +bool test_1() { + const char* T = "Inserted items should always exist"; + + myBloom<16> bloom; + + bloom.insert("apple"); + bloom.insert("banana"); + bloom.insert("carrot"); + + if (!bloom.exists("apple")) { + return failExample(T, "inserted item apple not found", "true", "false"); + } + + if (!bloom.exists("banana")) { + return failExample(T, "inserted item banana not found", "true", "false"); + } + + if (!bloom.exists("carrot")) { + return failExample(T, "inserted item carrot not found", "true", "false"); + } + + return true; +} + +// -------------------- makeBloom(filter) should reconstruct equivalent filter -------------------- +bool test_2() { + const char* T = "Bloom reconstructed from filter should preserve membership"; + + myBloom<16> original; + original.insert("dog"); + original.insert("cat"); + original.insert("mouse"); + + array saved = original.getFilter(); + + unique_ptr< Bloom<16> > rebuilt = original.makeBloom(saved); + + if (!rebuilt->exists("dog")) { + return failExample(T, "rebuilt bloom lost inserted item dog", "true", "false"); + } + + if (!rebuilt->exists("cat")) { + return failExample(T, "rebuilt bloom lost inserted item cat", "true", "false"); + } + + if (!rebuilt->exists("mouse")) { + return failExample(T, "rebuilt bloom lost inserted item mouse", "true", "false"); + } + + if (!sameFilter<16>(original, *rebuilt)) { + return failExample( + T, + "rebuilt bloom filter differs from original filter", + "same filter", + "different filter" + ); + } + + return true; +} + +// -------------------- makeBloom() should create a fresh empty bloom -------------------- +bool test_3() { + const char* T = "makeBloom() should create an empty bloom"; + + myBloom<16> original; + original.insert("apple"); + + unique_ptr< Bloom<16> > fresh = original.makeBloom(); + + if (fresh->exists("apple")) { + return failExample( + T, + "fresh bloom unexpectedly contains inserted item from another object", + "false", + "true" + ); + } + + return true; +} + +// -------------------- Multiple inserted items should remain detectable after more insertions -------------------- +bool test_4() { + const char* T = "Earlier inserted items should still exist after later insertions"; + + myBloom<32> bloom; + + bloom.insert("alpha"); + bloom.insert("beta"); + bloom.insert("gamma"); + bloom.insert("delta"); + bloom.insert("epsilon"); + bloom.insert("zeta"); + bloom.insert("eta"); + bloom.insert("theta"); + + const std::array inserted = { + "alpha", "beta", "gamma", "delta", + "epsilon", "zeta", "eta", "theta" + }; + + for (const auto& s : inserted) { + if (!bloom.exists(s)) { + return failExample( + T, + "an inserted item was later lost", + "true", + "false" + ); + } + } + + return true; +} + +// -------------------- No false negatives -------------------- + +bool test_5() { + + const char* T = "Bloom filter should have no false negatives"; + + myBloom<32> bf; + + // elements inserted + std::vector inserted = { + "apple", + "banana", + "carrot", + "dog", + "elephant" + }; + + // elements to check + std::vector check = { + "apple", + "banana", + "carrot", + "dog", + "elephant", + "fish", + "grape", + "house", + "island", + "jacket" + }; + + // insert elements + for (const auto& s : inserted) + bf.insert(s); + + // verify Bloom filter property + for (const auto& s : check) { + + bool exists = bf.exists(s); + + if (!exists) { + + // if Bloom says "not present", + // it must truly not have been inserted + if (std::find(inserted.begin(), inserted.end(), s) != inserted.end()) { + + return failExample( + T, + "Bloom filter produced a false negative", + "element should exist", + s + ); + } + } + } + + return true; +} + +int main() { + bool results[] = { test_0(), test_1(), test_2(), test_3(), test_4(), test_5() }; + + bool allPassed = true; + for (size_t ii = 0; ii < std::size(results); ii++) { + cout << "Test of problem " << to_string(ii) << ": " + << (results[ii] ? "passed" : "failed") << endl; + allPassed &= results[ii]; + } + + if (allPassed) exit(0); + else exit(-1); +} \ No newline at end of file diff --git a/tests/testHash.cpp b/tests/testHash.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4158e816d2678387629bcf6dc4df28f3169fe4b4 --- /dev/null +++ b/tests/testHash.cpp @@ -0,0 +1,75 @@ +// +// Created by Kevin on 3/9/2026. +// + +#include +#include +#include +#include +#include + +using namespace std; + +#include "impl/MiningHash.cpp" + +// helper function: consistent failure printing +bool failExample(const char* testName, + const std::string& msg, + const std::string& expected, + const std::string& got) { + std::cerr << "[" << testName << " FAILED] " << msg + << ": expected " << expected << ", got " << got << "\n"; + return false; +} + +// -------------------- Provided example 1 -------------------- +bool test_0() { + const char* T = "Provided example with nonce 2"; + + string got = mining_hash("The quick brown fox jumps over the lazy dog.", 2ULL); + string expected = "114129918868003668674860640176"; + + if (got != expected) { + return failExample( + T, + "hash output does not match provided example", + expected, + got + ); + } + + return true; +} + +// -------------------- Provided example 2 -------------------- +bool test_1() { + const char* T = "Provided example with nonce 155"; + + string got = mining_hash("The quick brown fox jumps over the lazy dog.", 155ULL); + string expected = "224347652901828190347249710002"; + + if (got != expected) { + return failExample( + T, + "hash output does not match provided example", + expected, + got + ); + } + + return true; +} + +int main() { + bool results[] = { test_0(), test_1() }; + + bool allPassed = true; + for (size_t ii = 0; ii < std::size(results); ii++) { + cout << "Test of problem " << to_string(ii) << ": " + << (results[ii] ? "passed" : "failed") << endl; + allPassed &= results[ii]; + } + + if (allPassed) exit(0); + else exit(-1); +} \ No newline at end of file