diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bff5ec..657e4db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,25 +9,16 @@ on: jobs: build: runs-on: ubuntu-latest - + steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install Dependencies - run: sudo apt-get install -y build-essential ffmpeg wget - - - name: Create Tests Directory - run: mkdir -p tests # Create the tests directory if it doesn't exist - - - name: Download Sample Video - run: wget -O tests/sample_video.mp4 https://www.pexels.com/video/3195394/download/ + run: sudo apt-get update && sudo apt-get install -y build-essential ffmpeg - name: Build - run: | - g++ -o davinci-convert src/main.cpp -lstdc++fs + run: make - - name: Run - run: | - echo "Running the Video Converter..." - ./davinci-convert tests/sample_video.mp4 --output tests/output/video.mov + - name: Run Unit Tests + run: make test diff --git a/.gitignore b/.gitignore index cb035de..9be50ec 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,12 @@ PKGBUILD # Devcontainer -.devcontainer/.vscode \ No newline at end of file +.devcontainer/.vscode + +# Test files +tests/*.mp4 +tests/*.mov +tests/*.mkv +tests/test_parser +tests/test_validator +tests/test_converter \ No newline at end of file diff --git a/Makefile b/Makefile index 78fd315..5cc17c1 100644 --- a/Makefile +++ b/Makefile @@ -28,11 +28,32 @@ install: $(TARGET) uninstall: rm -f /usr/local/bin/$(TARGET) -test: $(TARGET) - @echo "Running tests..." +test: $(TARGET) build_tests + @echo "Running integration tests..." @./$(TARGET) --help @echo "Test: Help command passed" @./$(TARGET) 2>&1 | grep -q "Usage:" && echo "Test: No args passed" || (echo "Test failed" && exit 1) + @echo "Running unit tests..." + @./tests/test_parser + @./tests/test_validator + @./tests/test_converter @echo "All tests passed!" -.PHONY: all clean install uninstall test +build_tests: test_parser_bin test_validator_bin test_converter_bin + +test_parser_bin: tests/test_parser.cpp src/parser.cpp src/include/config.hpp + @mkdir -p tests + $(CXX) $(CXXFLAGS) $(INCLUDES) -o tests/test_parser tests/test_parser.cpp src/parser.cpp + +test_validator_bin: tests/test_validator.cpp src/validator.cpp src/include/config.hpp + @mkdir -p tests + $(CXX) $(CXXFLAGS) $(INCLUDES) -o tests/test_validator tests/test_validator.cpp src/validator.cpp + +test_converter_bin: tests/test_converter.cpp src/converter.cpp src/include/config.hpp + @mkdir -p tests + $(CXX) $(CXXFLAGS) $(INCLUDES) -o tests/test_converter tests/test_converter.cpp src/converter.cpp + +clean-tests: + rm -f tests/test_parser tests/test_validator tests/test_converter + +.PHONY: all clean install uninstall test build_tests clean-tests diff --git a/tests/test_converter.cpp b/tests/test_converter.cpp new file mode 100644 index 0000000..1fd1d9e --- /dev/null +++ b/tests/test_converter.cpp @@ -0,0 +1,141 @@ +#include "../src/include/converter.hpp" +#include +#include +#include + +void test_build_command_basic() { + Config config; + config.input_path = "input.mp4"; + config.output_path = "output.mp4"; + + std::string command = build_ffmpeg_command(config); + + assert(command.find("ffmpeg") != std::string::npos); + assert(command.find("-i \"input.mp4\"") != std::string::npos); + assert(command.find("-c:v libx264") != std::string::npos); + assert(command.find("\"output.mp4\"") != std::string::npos); + + std::cout << "test_build_command_basic: PASSED" << std::endl; +} + +void test_build_command_custom_codec() { + Config config; + config.input_path = "input.mov"; + config.output_path = "output.mkv"; + config.codec = "h265"; + + std::string command = build_ffmpeg_command(config); + + assert(command.find("-c:v libx265") != std::string::npos); + assert(command.find("-i \"input.mov\"") != std::string::npos); + assert(command.find("\"output.mkv\"") != std::string::npos); + + std::cout << "test_build_command_custom_codec: PASSED" << std::endl; +} + +void test_build_command_with_crf() { + Config config; + config.input_path = "input.mp4"; + config.output_path = "output.mp4"; + config.crf = 18; + + std::string command = build_ffmpeg_command(config); + + assert(command.find("-crf 18") != std::string::npos); + + std::cout << "test_build_command_with_crf: PASSED" << std::endl; +} + +void test_build_command_quality_preset() { + Config config; + config.input_path = "input.mp4"; + config.output_path = "output.mp4"; + config.quality = "slow"; + + std::string command = build_ffmpeg_command(config); + + assert(command.find("-preset slow") != std::string::npos); + + std::cout << "test_build_command_quality_preset: PASSED" << std::endl; +} + +void test_build_command_fast_quality() { + Config config; + config.input_path = "input.mp4"; + config.output_path = "output.mp4"; + config.quality = "fast"; + + std::string command = build_ffmpeg_command(config); + + assert(command.find("-preset fast") != std::string::npos); + + std::cout << "test_build_command_fast_quality: PASSED" << std::endl; +} + +void test_build_command_medium_quality() { + Config config; + config.input_path = "input.mp4"; + config.output_path = "output.mp4"; + config.quality = "medium"; + + std::string command = build_ffmpeg_command(config); + + assert(command.find("-preset medium") != std::string::npos); + + std::cout << "test_build_command_medium_quality: PASSED" << std::endl; +} + +void test_build_command_prores() { + Config config; + config.input_path = "input.mov"; + config.output_path = "output.mov"; + config.codec = "prores"; + + std::string command = build_ffmpeg_command(config); + + assert(command.find("-c:v prores_ks") != std::string::npos); + + std::cout << "test_build_command_prores: PASSED" << std::endl; +} + +void test_build_command_verbose() { + Config config; + config.input_path = "input.mp4"; + config.output_path = "output.mp4"; + config.verbose = true; + + std::string command = build_ffmpeg_command(config); + + assert(command.find("-loglevel verbose") != std::string::npos); + + std::cout << "test_build_command_verbose: PASSED" << std::endl; +} + +void test_build_command_audio_copy() { + Config config; + config.input_path = "input.mp4"; + config.output_path = "output.mp4"; + + std::string command = build_ffmpeg_command(config); + + assert(command.find("-c:a copy") != std::string::npos); + + std::cout << "test_build_command_audio_copy: PASSED" << std::endl; +} + +int main() { + std::cout << "=== Converter Tests ===" << std::endl; + + test_build_command_basic(); + test_build_command_custom_codec(); + test_build_command_with_crf(); + test_build_command_quality_preset(); + test_build_command_fast_quality(); + test_build_command_medium_quality(); + test_build_command_prores(); + test_build_command_verbose(); + test_build_command_audio_copy(); + + std::cout << "All converter tests passed!" << std::endl; + return 0; +} diff --git a/tests/test_parser.cpp b/tests/test_parser.cpp new file mode 100644 index 0000000..d3a167c --- /dev/null +++ b/tests/test_parser.cpp @@ -0,0 +1,80 @@ +#include "../src/include/parser.hpp" +#include +#include +#include + +void test_parse_help() { + // Help triggers print_usage and exits, so we skip this test + std::cout << "test_parse_help: skipped (exits on --help)" << std::endl; +} + +void test_parse_default_values() { + char* args[] = {(char*)"program", (char*)"input.mp4", (char*)"output.mp4", nullptr}; + Config config = parse_arguments(3, args); + + assert(config.input_path == "input.mp4"); + assert(config.output_path == "output.mp4"); + assert(config.codec == "h264"); + assert(config.quality == "medium"); + assert(config.crf == 23); + assert(config.verbose == false); + + std::cout << "test_parse_default_values: PASSED" << std::endl; +} + +void test_parse_custom_values() { + char* args[] = { + (char*)"program", + (char*)"input.mov", + (char*)"output.mkv", + (char*)"-c", (char*)"hevc", + (char*)"-q", (char*)"slow", + (char*)"-r", (char*)"18", + (char*)"-v", + nullptr + }; + Config config = parse_arguments(10, args); + + assert(config.input_path == "input.mov"); + assert(config.output_path == "output.mkv"); + assert(config.codec == "hevc"); + assert(config.quality == "slow"); + assert(config.crf == 18); + assert(config.verbose == true); + + std::cout << "test_parse_custom_values: PASSED" << std::endl; +} + +void test_parse_long_options() { + char* args[] = { + (char*)"program", + (char*)"src.mp4", + (char*)"dst.mp4", + (char*)"--codec", (char*)"prores", + (char*)"--quality", (char*)"fast", + (char*)"--crf", (char*)"28", + (char*)"--verbose", + nullptr + }; + Config config = parse_arguments(10, args); + + assert(config.input_path == "src.mp4"); + assert(config.output_path == "dst.mp4"); + assert(config.codec == "prores"); + assert(config.quality == "fast"); + assert(config.crf == 28); + assert(config.verbose == true); + + std::cout << "test_parse_long_options: PASSED" << std::endl; +} + +int main() { + std::cout << "=== Parser Tests ===" << std::endl; + + test_parse_default_values(); + test_parse_custom_values(); + test_parse_long_options(); + + std::cout << "All parser tests passed!" << std::endl; + return 0; +} diff --git a/tests/test_validator.cpp b/tests/test_validator.cpp new file mode 100644 index 0000000..04dde9b --- /dev/null +++ b/tests/test_validator.cpp @@ -0,0 +1,150 @@ +#include "../src/include/validator.hpp" +#include +#include + +void test_valid_config() { + Config config; + config.input_path = "tests/input.mp4"; + config.output_path = "tests/output.mp4"; + config.codec = "h264"; + config.quality = "medium"; + config.crf = 23; + + std::string error; + bool result = validate_config(config, error); + + assert(result == true); + assert(error.empty()); + + std::cout << "test_valid_config: PASSED" << std::endl; +} + +void test_missing_input() { + Config config; + config.input_path = ""; + config.output_path = "tests/output.mp4"; + + std::string error; + bool result = validate_config(config, error); + + assert(result == false); + assert(!error.empty()); + + std::cout << "test_missing_input: PASSED" << std::endl; +} + +void test_missing_output() { + Config config; + config.input_path = "tests/input.mp4"; + config.output_path = ""; + + std::string error; + bool result = validate_config(config, error); + + assert(result == false); + assert(!error.empty()); + + std::cout << "test_missing_output: PASSED" << std::endl; +} + +void test_invalid_codec() { + Config config; + config.input_path = "tests/input.mp4"; + config.output_path = "tests/output.mp4"; + config.codec = "invalid_codec"; + + std::string error; + bool result = validate_config(config, error); + + assert(result == false); + assert(!error.empty()); + + std::cout << "test_invalid_codec: PASSED" << std::endl; +} + +void test_invalid_quality() { + Config config; + config.input_path = "tests/input.mp4"; + config.output_path = "tests/output.mp4"; + config.quality = "ultra_invalid"; + + std::string error; + bool result = validate_config(config, error); + + assert(result == false); + assert(!error.empty()); + + std::cout << "test_invalid_quality: PASSED" << std::endl; +} + +void test_crf_range() { + // CRF too low + Config config1; + config1.input_path = "tests/input.mp4"; + config1.output_path = "tests/output.mp4"; + config1.crf = -1; + + std::string error1; + assert(validate_config(config1, error1) == false); + + // CRF too high + Config config2; + config2.input_path = "tests/input.mp4"; + config2.output_path = "tests/output.mp4"; + config2.crf = 60; + + std::string error2; + assert(validate_config(config2, error2) == false); + + std::cout << "test_crf_range: PASSED" << std::endl; +} + +void test_valid_codecs() { + const char* valid_codecs[] = {"h264", "h265", "prores"}; + + for (const char* codec : valid_codecs) { + Config config; + config.input_path = "tests/input.mp4"; + config.output_path = "tests/output.mp4"; + config.codec = codec; + + std::string error; + bool result = validate_config(config, error); + assert(result == true); + } + + std::cout << "test_valid_codecs: PASSED" << std::endl; +} + +void test_valid_qualities() { + const char* valid_qualities[] = {"fast", "medium", "slow"}; + + for (const char* quality : valid_qualities) { + Config config; + config.input_path = "tests/input.mp4"; + config.output_path = "tests/output.mp4"; + config.quality = quality; + + std::string error; + bool result = validate_config(config, error); + assert(result == true); + } + + std::cout << "test_valid_qualities: PASSED" << std::endl; +} + +int main() { + std::cout << "=== Validator Tests ===" << std::endl; + + test_valid_config(); + test_missing_input(); + test_missing_output(); + test_invalid_codec(); + test_invalid_quality(); + test_crf_range(); + test_valid_codecs(); + test_valid_qualities(); + + std::cout << "All validator tests passed!" << std::endl; + return 0; +}