Modify dehydrate/test.sh
Some checks failed
Build-Test-Publish / build (linux/arm64) (push) Failing after 9s
Build-Test-Publish / build (linux/amd64) (push) Successful in 26s
Build-Test-Publish / test-install-from-scratch (linux/amd64) (push) Has been skipped
Build-Test-Publish / test-install-from-scratch (linux/arm64) (push) Has been skipped
Some checks failed
Build-Test-Publish / build (linux/arm64) (push) Failing after 9s
Build-Test-Publish / build (linux/amd64) (push) Successful in 26s
Build-Test-Publish / test-install-from-scratch (linux/amd64) (push) Has been skipped
Build-Test-Publish / test-install-from-scratch (linux/arm64) (push) Has been skipped
This commit is contained in:
parent
3d4744b7b1
commit
95da0d28d4
@ -121,15 +121,33 @@ fi
|
|||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
|
|
||||||
# Print summary
|
# Print summary for basic tests
|
||||||
echo -e "\n${YELLOW}Test Summary:${NC}"
|
echo -e "\n${YELLOW}Basic Test Summary:${NC}"
|
||||||
echo -e "Tests passed: ${GREEN}${TESTS_PASSED}${NC}"
|
echo -e "Tests passed: ${GREEN}${TESTS_PASSED}${NC}"
|
||||||
echo -e "Tests failed: ${RED}${TESTS_FAILED}${NC}"
|
echo -e "Tests failed: ${RED}${TESTS_FAILED}${NC}"
|
||||||
|
|
||||||
|
# Run comprehensive tests if basic tests passed
|
||||||
if [ "$TESTS_FAILED" -eq 0 ]; then
|
if [ "$TESTS_FAILED" -eq 0 ]; then
|
||||||
|
echo -e "\n${YELLOW}Running comprehensive tests...${NC}"
|
||||||
|
if [ -f "$SCRIPT_DIR/test/test.sh" ]; then
|
||||||
|
cd "$SCRIPT_DIR/test"
|
||||||
|
./test.sh
|
||||||
|
COMPREHENSIVE_RESULT=$?
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
if [ "$COMPREHENSIVE_RESULT" -eq 0 ]; then
|
||||||
echo -e "\n${GREEN}All tests passed!${NC}"
|
echo -e "\n${GREEN}All tests passed!${NC}"
|
||||||
exit 0
|
exit 0
|
||||||
|
else
|
||||||
|
echo -e "\n${RED}Comprehensive tests failed!${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Warning: Comprehensive test suite not found${NC}"
|
||||||
|
echo -e "\n${GREEN}Basic tests passed!${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo -e "\n${RED}Some tests failed!${NC}"
|
echo -e "\n${RED}Basic tests failed! Skipping comprehensive tests.${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
16
dehydrate/test/build_dehydrate_test.sh
Executable file
16
dehydrate/test/build_dehydrate_test.sh
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Get the directory where this script is located
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Clean up old test data
|
||||||
|
rm -rf dehydrate_test_data
|
||||||
|
|
||||||
|
# Build the test program
|
||||||
|
docker run --rm -v $(pwd):/workdir -w /workdir gitea.jde.nz/public/dropshell-build-base:latest \
|
||||||
|
g++ -std=c++23 -static dehydrate_test.cpp -o dehydrate_test
|
||||||
|
|
||||||
|
# Run the test
|
||||||
|
./dehydrate_test
|
BIN
dehydrate/test/dehydrate_test
Executable file
BIN
dehydrate/test/dehydrate_test
Executable file
Binary file not shown.
365
dehydrate/test/dehydrate_test.cpp
Normal file
365
dehydrate/test/dehydrate_test.cpp
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
// Helper function to execute a command and capture its output
|
||||||
|
bool execute_command(const std::string& cmd, std::string& output) {
|
||||||
|
FILE* pipe = popen(cmd.c_str(), "r");
|
||||||
|
if (!pipe) return false;
|
||||||
|
|
||||||
|
char buffer[128];
|
||||||
|
output.clear();
|
||||||
|
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
||||||
|
output += buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = pclose(pipe);
|
||||||
|
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to calculate file hash (simple checksum for testing)
|
||||||
|
std::string calculate_file_hash(const fs::path& filepath) {
|
||||||
|
std::ifstream file(filepath, std::ios::binary);
|
||||||
|
if (!file.is_open()) return "";
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << file.rdbuf();
|
||||||
|
std::string content = oss.str();
|
||||||
|
|
||||||
|
// Simple hash for demonstration
|
||||||
|
size_t hash = 0;
|
||||||
|
for (char c : content) {
|
||||||
|
hash = hash * 31 + static_cast<unsigned char>(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << hash;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to compare two files
|
||||||
|
bool compare_files(const fs::path& file1, const fs::path& file2) {
|
||||||
|
// Compare existence
|
||||||
|
if (!fs::exists(file1) || !fs::exists(file2)) {
|
||||||
|
std::cout << "File existence mismatch: " << file1 << " vs " << file2 << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare size
|
||||||
|
if (fs::file_size(file1) != fs::file_size(file2)) {
|
||||||
|
std::cout << "File size mismatch: " << file1 << " (" << fs::file_size(file1)
|
||||||
|
<< ") vs " << file2 << " (" << fs::file_size(file2) << ")" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare content
|
||||||
|
std::ifstream f1(file1, std::ios::binary);
|
||||||
|
std::ifstream f2(file2, std::ios::binary);
|
||||||
|
|
||||||
|
std::string content1((std::istreambuf_iterator<char>(f1)), std::istreambuf_iterator<char>());
|
||||||
|
std::string content2((std::istreambuf_iterator<char>(f2)), std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
|
if (content1 != content2) {
|
||||||
|
std::cout << "File content mismatch: " << file1 << " vs " << file2 << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare permissions
|
||||||
|
auto perms1 = fs::status(file1).permissions();
|
||||||
|
auto perms2 = fs::status(file2).permissions();
|
||||||
|
|
||||||
|
if (perms1 != perms2) {
|
||||||
|
std::cout << "File permissions mismatch: " << file1
|
||||||
|
<< " (" << static_cast<unsigned>(perms1) << ") vs "
|
||||||
|
<< file2 << " (" << static_cast<unsigned>(perms2) << ")" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to compare directories recursively
|
||||||
|
bool compare_directories(const fs::path& dir1, const fs::path& dir2) {
|
||||||
|
std::vector<fs::path> files1, files2;
|
||||||
|
|
||||||
|
// Collect all files from dir1
|
||||||
|
for (const auto& entry : fs::recursive_directory_iterator(dir1)) {
|
||||||
|
if (fs::is_regular_file(entry)) {
|
||||||
|
files1.push_back(fs::relative(entry.path(), dir1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all files from dir2
|
||||||
|
for (const auto& entry : fs::recursive_directory_iterator(dir2)) {
|
||||||
|
if (fs::is_regular_file(entry)) {
|
||||||
|
files2.push_back(fs::relative(entry.path(), dir2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort for comparison
|
||||||
|
std::sort(files1.begin(), files1.end());
|
||||||
|
std::sort(files2.begin(), files2.end());
|
||||||
|
|
||||||
|
// Check if same files exist
|
||||||
|
if (files1 != files2) {
|
||||||
|
std::cout << "Directory structure mismatch!" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare each file
|
||||||
|
bool all_match = true;
|
||||||
|
for (const auto& rel_path : files1) {
|
||||||
|
if (!compare_files(dir1 / rel_path, dir2 / rel_path)) {
|
||||||
|
all_match = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return all_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "=== Dehydrate Test Program ===" << std::endl;
|
||||||
|
|
||||||
|
// Create test directory structure
|
||||||
|
fs::path test_root = "dehydrate_test_data";
|
||||||
|
fs::path original_dir = test_root / "original";
|
||||||
|
fs::path generated_dir = test_root / "generated";
|
||||||
|
fs::path recreated_dir = test_root / "recreated";
|
||||||
|
|
||||||
|
// Clean up any existing test data
|
||||||
|
if (fs::exists(test_root)) {
|
||||||
|
fs::remove_all(test_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create directories
|
||||||
|
fs::create_directories(original_dir / "subdir");
|
||||||
|
fs::create_directories(generated_dir);
|
||||||
|
fs::create_directories(recreated_dir);
|
||||||
|
|
||||||
|
std::cout << "\n1. Creating test files..." << std::endl;
|
||||||
|
|
||||||
|
// Create test file 1: Simple text file
|
||||||
|
{
|
||||||
|
std::ofstream file(original_dir / "test1.txt");
|
||||||
|
file << "This is a simple text file.\nIt has multiple lines.\nLine 3.";
|
||||||
|
file.close();
|
||||||
|
fs::permissions(original_dir / "test1.txt", fs::perms::owner_read | fs::perms::owner_write);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test file 2: Binary file with special characters
|
||||||
|
{
|
||||||
|
std::ofstream file(original_dir / "test2.bin", std::ios::binary);
|
||||||
|
unsigned char binary_data[] = {0x00, 0xFF, 0x42, 0x13, 0x37, 0xDE, 0xAD, 0xBE, 0xEF};
|
||||||
|
file.write(reinterpret_cast<char*>(binary_data), sizeof(binary_data));
|
||||||
|
file.close();
|
||||||
|
fs::permissions(original_dir / "test2.bin", fs::perms::owner_all);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test file 3: Executable script
|
||||||
|
{
|
||||||
|
std::ofstream file(original_dir / "test3.sh");
|
||||||
|
file << "#!/bin/bash\necho 'Hello from test script'\nexit 0";
|
||||||
|
file.close();
|
||||||
|
fs::permissions(original_dir / "test3.sh",
|
||||||
|
fs::perms::owner_all | fs::perms::group_read | fs::perms::group_exec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test file 4: File in subdirectory
|
||||||
|
{
|
||||||
|
std::ofstream file(original_dir / "subdir" / "nested.txt");
|
||||||
|
file << "This file is in a subdirectory.";
|
||||||
|
file.close();
|
||||||
|
fs::permissions(original_dir / "subdir" / "nested.txt", fs::perms::owner_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test file 5: Very small file
|
||||||
|
{
|
||||||
|
std::ofstream file(original_dir / "small.txt");
|
||||||
|
file << "x"; // Single character to avoid empty file
|
||||||
|
file.close();
|
||||||
|
fs::permissions(original_dir / "small.txt", fs::perms::owner_read | fs::perms::owner_write);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Created test files in: " << original_dir << std::endl;
|
||||||
|
|
||||||
|
// Build dehydrate if not already built
|
||||||
|
std::cout << "\n2. Building dehydrate tool..." << std::endl;
|
||||||
|
std::string output;
|
||||||
|
if (!fs::exists("../output/dehydrate")) {
|
||||||
|
if (!execute_command("cd .. && ./build.sh", output)) {
|
||||||
|
std::cerr << "Failed to build dehydrate!" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test single file dehydration
|
||||||
|
std::cout << "\n3. Testing single file dehydration..." << std::endl;
|
||||||
|
{
|
||||||
|
std::string cmd = "../output/dehydrate -s " +
|
||||||
|
(original_dir / "test1.txt").string() + " " +
|
||||||
|
generated_dir.string();
|
||||||
|
|
||||||
|
if (!execute_command(cmd, output)) {
|
||||||
|
std::cerr << "Failed to dehydrate single file!" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if generated files exist
|
||||||
|
if (!fs::exists(generated_dir / "_test1.cpp") ||
|
||||||
|
!fs::exists(generated_dir / "_test1.hpp")) {
|
||||||
|
std::cerr << "Generated files not found!" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Generated: _test1.cpp and _test1.hpp" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test directory dehydration
|
||||||
|
std::cout << "\n4. Testing directory dehydration..." << std::endl;
|
||||||
|
{
|
||||||
|
std::string cmd = "../output/dehydrate -s " +
|
||||||
|
original_dir.string() + " " +
|
||||||
|
generated_dir.string();
|
||||||
|
|
||||||
|
if (!execute_command(cmd, output)) {
|
||||||
|
std::cerr << "Failed to dehydrate directory!" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if generated files exist
|
||||||
|
if (!fs::exists(generated_dir / "_original.cpp") ||
|
||||||
|
!fs::exists(generated_dir / "_original.hpp")) {
|
||||||
|
std::cerr << "Generated directory files not found!" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Generated: _original.cpp and _original.hpp" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test program that uses the generated code
|
||||||
|
std::cout << "\n5. Creating test program to recreate files..." << std::endl;
|
||||||
|
{
|
||||||
|
std::ofstream test_prog(test_root / "test_recreate.cpp");
|
||||||
|
test_prog << R"cpp(
|
||||||
|
#include <iostream>
|
||||||
|
#include "generated/_test1.hpp"
|
||||||
|
#include "generated/_original.hpp"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Testing file recreation..." << std::endl;
|
||||||
|
|
||||||
|
// Test single file recreation
|
||||||
|
std::cout << "Recreating single file..." << std::endl;
|
||||||
|
if (recreate_test1::recreate_file("recreated")) {
|
||||||
|
std::cout << "Single file recreation returned true" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test directory recreation
|
||||||
|
std::cout << "Recreating directory tree..." << std::endl;
|
||||||
|
if (recreate_original::recreate_tree("recreated/tree")) {
|
||||||
|
std::cout << "Directory recreation returned true" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
)cpp";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile the test program
|
||||||
|
std::cout << "\n6. Compiling recreation test program..." << std::endl;
|
||||||
|
{
|
||||||
|
std::string cmd = "cd " + test_root.string() +
|
||||||
|
" && g++ -std=c++23 -static -I. test_recreate.cpp generated/_test1.cpp generated/_original.cpp" +
|
||||||
|
" -o test_recreate";
|
||||||
|
|
||||||
|
if (!execute_command(cmd, output)) {
|
||||||
|
std::cerr << "Failed to compile test program!" << std::endl;
|
||||||
|
std::cerr << "Output: " << output << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the recreation test
|
||||||
|
std::cout << "\n7. Running recreation test..." << std::endl;
|
||||||
|
{
|
||||||
|
std::string cmd = "cd " + test_root.string() + " && ./test_recreate";
|
||||||
|
|
||||||
|
if (!execute_command(cmd, output)) {
|
||||||
|
std::cerr << "Failed to run recreation test!" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << output << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare results
|
||||||
|
std::cout << "\n8. Comparing original and recreated files..." << std::endl;
|
||||||
|
|
||||||
|
// Compare single file
|
||||||
|
std::cout << "\nComparing single file recreation:" << std::endl;
|
||||||
|
if (compare_files(original_dir / "test1.txt", recreated_dir / "test1.txt")) {
|
||||||
|
std::cout << "✓ Single file matches!" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "✗ Single file does NOT match!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare directory tree
|
||||||
|
std::cout << "\nComparing directory tree recreation:" << std::endl;
|
||||||
|
if (compare_directories(original_dir, recreated_dir / "tree")) {
|
||||||
|
std::cout << "✓ Directory tree matches!" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "✗ Directory tree does NOT match!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test re-running recreation (should detect no changes needed)
|
||||||
|
std::cout << "\n9. Testing re-run (should detect no changes)..." << std::endl;
|
||||||
|
{
|
||||||
|
std::string cmd = "cd " + test_root.string() + " && ./test_recreate";
|
||||||
|
|
||||||
|
if (!execute_command(cmd, output)) {
|
||||||
|
std::cerr << "Failed to re-run recreation test!" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << output << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify a file and test update detection
|
||||||
|
std::cout << "\n10. Testing update detection..." << std::endl;
|
||||||
|
{
|
||||||
|
// Modify the recreated file
|
||||||
|
std::ofstream file(recreated_dir / "test1.txt");
|
||||||
|
file << "Modified content";
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// Re-run recreation
|
||||||
|
std::string cmd = "cd " + test_root.string() + " && ./test_recreate";
|
||||||
|
|
||||||
|
if (!execute_command(cmd, output)) {
|
||||||
|
std::cerr << "Failed to test update!" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << output << std::endl;
|
||||||
|
|
||||||
|
// Verify it was restored
|
||||||
|
if (compare_files(original_dir / "test1.txt", recreated_dir / "test1.txt")) {
|
||||||
|
std::cout << "✓ File correctly restored after modification!" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "✗ File NOT restored correctly!" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n=== Test Complete ===" << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
188
dehydrate/test/dehydrate_test_data/generated/_original.cpp
Normal file
188
dehydrate/test/dehydrate_test_data/generated/_original.cpp
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
THIS FILE IS AUTO-GENERATED BY DEHYDRATE.
|
||||||
|
DO NOT EDIT THIS FILE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "_original.hpp"
|
||||||
|
namespace recreate_original {
|
||||||
|
|
||||||
|
|
||||||
|
// Tiny dependency-free FNV-1a 64-bit hash
|
||||||
|
static uint64_t fnv1a_64(const void* data, size_t len) {
|
||||||
|
const uint8_t* p = static_cast<const uint8_t*>(data);
|
||||||
|
uint64_t h = 0xcbf29ce484222325ULL;
|
||||||
|
for (size_t i = 0; i < len; ++i)
|
||||||
|
h = (h ^ p[i]) * 0x100000001b3ULL;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Base64 decoding function - no dependencies
|
||||||
|
static void base64_decode(const char* encoded_data, size_t encoded_len, unsigned char* output, size_t* output_len) {
|
||||||
|
const char* base64_chars =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
size_t out_pos = 0;
|
||||||
|
int val = 0, valb = -8;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < encoded_len; i++) {
|
||||||
|
char c = encoded_data[i];
|
||||||
|
if (c == '=') break;
|
||||||
|
|
||||||
|
// Find position in base64_chars
|
||||||
|
const char* pos = strchr(base64_chars, c);
|
||||||
|
if (pos == nullptr) continue; // Skip invalid characters
|
||||||
|
|
||||||
|
val = (val << 6) + static_cast<int>(pos - base64_chars);
|
||||||
|
valb += 6;
|
||||||
|
if (valb >= 0) {
|
||||||
|
output[out_pos++] = static_cast<unsigned char>((val >> valb) & 0xFF);
|
||||||
|
valb -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_len = out_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function to recreate a file with proper permissions
|
||||||
|
static bool _recreate_file_(const std::filesystem::path& outpath, uint64_t file_hash, std::filesystem::perms file_perms, const unsigned char* filedata, size_t filedata_len) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
bool needs_write = false;
|
||||||
|
|
||||||
|
// Check if file exists and has correct hash
|
||||||
|
if (fs::exists(outpath)) {
|
||||||
|
// Check content hash
|
||||||
|
std::ifstream in(outpath, std::ios::binary);
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << in.rdbuf();
|
||||||
|
std::string data = oss.str();
|
||||||
|
uint64_t existing_hash = fnv1a_64(data.data(), data.size());
|
||||||
|
needs_write = existing_hash != file_hash;
|
||||||
|
} else {
|
||||||
|
needs_write = true; // File doesn't exist, need to create it
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needs_permission_update = true;
|
||||||
|
if (!needs_write) { // we always update permissions if the file is written or changed. Othewise we check.
|
||||||
|
fs::perms current_perms = fs::status(outpath).permissions();
|
||||||
|
needs_permission_update = current_perms != file_perms;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_write) {
|
||||||
|
bool existed = fs::exists(outpath);
|
||||||
|
|
||||||
|
fs::create_directories(outpath.parent_path());
|
||||||
|
std::ofstream out(outpath, std::ios::binary);
|
||||||
|
out.write(reinterpret_cast<const char*>(filedata), filedata_len);
|
||||||
|
out.close();
|
||||||
|
// Set the file permissions
|
||||||
|
fs::permissions(outpath, file_perms);
|
||||||
|
|
||||||
|
if (!existed) {
|
||||||
|
std::cout << "[dehydrate] " << outpath.filename() << ": created\n";
|
||||||
|
} else {
|
||||||
|
std::cout << "[dehydrate] " << outpath.filename() << ": updated (hash changed)\n";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_permission_update) {
|
||||||
|
// Update only permissions
|
||||||
|
fs::permissions(outpath, file_perms);
|
||||||
|
std::cout << "[dehydrate] " << outpath.filename() << ": updated (permissions changed)\n";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool recreate_tree(std::string destination_folder) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
bool any_written = false;
|
||||||
|
{
|
||||||
|
// File: small.txt
|
||||||
|
fs::path outpath = fs::path(destination_folder) / "small.txt";
|
||||||
|
static const char filedata_base64[] = "eA==";
|
||||||
|
|
||||||
|
// Decode Base64 data
|
||||||
|
size_t decoded_size = (strlen(filedata_base64) * 3) / 4;
|
||||||
|
unsigned char* decoded_data = new unsigned char[decoded_size];
|
||||||
|
size_t actual_size;
|
||||||
|
base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);
|
||||||
|
|
||||||
|
bool file_written = _recreate_file_(outpath, 12638214688346347271ULL, std::filesystem::perms(384), decoded_data, actual_size);
|
||||||
|
delete[] decoded_data;
|
||||||
|
any_written = any_written || file_written;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// File: test2.bin
|
||||||
|
fs::path outpath = fs::path(destination_folder) / "test2.bin";
|
||||||
|
static const char filedata_base64[] = "AP9CEzferb7v";
|
||||||
|
|
||||||
|
// Decode Base64 data
|
||||||
|
size_t decoded_size = (strlen(filedata_base64) * 3) / 4;
|
||||||
|
unsigned char* decoded_data = new unsigned char[decoded_size];
|
||||||
|
size_t actual_size;
|
||||||
|
base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);
|
||||||
|
|
||||||
|
bool file_written = _recreate_file_(outpath, 10042072622899139650ULL, std::filesystem::perms(448), decoded_data, actual_size);
|
||||||
|
delete[] decoded_data;
|
||||||
|
any_written = any_written || file_written;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// File: test3.sh
|
||||||
|
fs::path outpath = fs::path(destination_folder) / "test3.sh";
|
||||||
|
static const char filedata_base64[] = "IyEvYmluL2Jhc2gKZWNobyAnSGVsbG8gZnJvbSB0ZXN0IHNjcmlwdCcKZXhpdCAw";
|
||||||
|
|
||||||
|
// Decode Base64 data
|
||||||
|
size_t decoded_size = (strlen(filedata_base64) * 3) / 4;
|
||||||
|
unsigned char* decoded_data = new unsigned char[decoded_size];
|
||||||
|
size_t actual_size;
|
||||||
|
base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);
|
||||||
|
|
||||||
|
bool file_written = _recreate_file_(outpath, 14335927320996074478ULL, std::filesystem::perms(488), decoded_data, actual_size);
|
||||||
|
delete[] decoded_data;
|
||||||
|
any_written = any_written || file_written;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// File: test1.txt
|
||||||
|
fs::path outpath = fs::path(destination_folder) / "test1.txt";
|
||||||
|
static const char filedata_base64[] = "VGhpcyBpcyBhIHNpbXBsZSB0ZXh0IGZpbGUuCkl0IGhhcyBtdWx0aXBsZSBsaW5lcy4KTGluZSAz"\
|
||||||
|
"Lg==";
|
||||||
|
|
||||||
|
// Decode Base64 data
|
||||||
|
size_t decoded_size = (strlen(filedata_base64) * 3) / 4;
|
||||||
|
unsigned char* decoded_data = new unsigned char[decoded_size];
|
||||||
|
size_t actual_size;
|
||||||
|
base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);
|
||||||
|
|
||||||
|
bool file_written = _recreate_file_(outpath, 11900461415522640014ULL, std::filesystem::perms(384), decoded_data, actual_size);
|
||||||
|
delete[] decoded_data;
|
||||||
|
any_written = any_written || file_written;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// File: subdir/nested.txt
|
||||||
|
fs::path outpath = fs::path(destination_folder) / "subdir/nested.txt";
|
||||||
|
static const char filedata_base64[] = "VGhpcyBmaWxlIGlzIGluIGEgc3ViZGlyZWN0b3J5Lg==";
|
||||||
|
|
||||||
|
// Decode Base64 data
|
||||||
|
size_t decoded_size = (strlen(filedata_base64) * 3) / 4;
|
||||||
|
unsigned char* decoded_data = new unsigned char[decoded_size];
|
||||||
|
size_t actual_size;
|
||||||
|
base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);
|
||||||
|
|
||||||
|
bool file_written = _recreate_file_(outpath, 14153000318456068100ULL, std::filesystem::perms(256), decoded_data, actual_size);
|
||||||
|
delete[] decoded_data;
|
||||||
|
any_written = any_written || file_written;
|
||||||
|
}
|
||||||
|
return any_written;
|
||||||
|
}
|
||||||
|
}
|
15
dehydrate/test/dehydrate_test_data/generated/_original.hpp
Normal file
15
dehydrate/test/dehydrate_test_data/generated/_original.hpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
THIS FILE IS AUTO-GENERATED BY DEHYDRATE.
|
||||||
|
DO NOT EDIT THIS FILE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
namespace recreate_original {
|
||||||
|
bool recreate_tree(std::string destination_folder);
|
||||||
|
}
|
114
dehydrate/test/dehydrate_test_data/generated/_test1.cpp
Normal file
114
dehydrate/test/dehydrate_test_data/generated/_test1.cpp
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// Tiny dependency-free FNV-1a 64-bit hash
|
||||||
|
static uint64_t fnv1a_64(const void* data, size_t len) {
|
||||||
|
const uint8_t* p = static_cast<const uint8_t*>(data);
|
||||||
|
uint64_t h = 0xcbf29ce484222325ULL;
|
||||||
|
for (size_t i = 0; i < len; ++i)
|
||||||
|
h = (h ^ p[i]) * 0x100000001b3ULL;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
#include "_test1.hpp"
|
||||||
|
namespace recreate_test1 {
|
||||||
|
|
||||||
|
// Base64 decoding function - no dependencies
|
||||||
|
static void base64_decode(const char* encoded_data, size_t encoded_len, unsigned char* output, size_t* output_len) {
|
||||||
|
const char* base64_chars =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
size_t out_pos = 0;
|
||||||
|
int val = 0, valb = -8;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < encoded_len; i++) {
|
||||||
|
char c = encoded_data[i];
|
||||||
|
if (c == '=') break;
|
||||||
|
|
||||||
|
// Find position in base64_chars
|
||||||
|
const char* pos = strchr(base64_chars, c);
|
||||||
|
if (pos == nullptr) continue; // Skip invalid characters
|
||||||
|
|
||||||
|
val = (val << 6) + static_cast<int>(pos - base64_chars);
|
||||||
|
valb += 6;
|
||||||
|
if (valb >= 0) {
|
||||||
|
output[out_pos++] = static_cast<unsigned char>((val >> valb) & 0xFF);
|
||||||
|
valb -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_len = out_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function to recreate a file with proper permissions
|
||||||
|
static bool _recreate_file_(const std::filesystem::path& outpath, uint64_t file_hash, std::filesystem::perms file_perms, const unsigned char* filedata, size_t filedata_len) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
bool needs_write = false;
|
||||||
|
|
||||||
|
// Check if file exists and has correct hash
|
||||||
|
if (fs::exists(outpath)) {
|
||||||
|
// Check content hash
|
||||||
|
std::ifstream in(outpath, std::ios::binary);
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << in.rdbuf();
|
||||||
|
std::string data = oss.str();
|
||||||
|
uint64_t existing_hash = fnv1a_64(data.data(), data.size());
|
||||||
|
needs_write = existing_hash != file_hash;
|
||||||
|
} else {
|
||||||
|
needs_write = true; // File doesn't exist, need to create it
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needs_permission_update = true;
|
||||||
|
if (!needs_write) { // we always update permissions if the file is written or changed. Othewise we check.
|
||||||
|
fs::perms current_perms = fs::status(outpath).permissions();
|
||||||
|
needs_permission_update = current_perms != file_perms;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_write) {
|
||||||
|
bool existed = fs::exists(outpath);
|
||||||
|
|
||||||
|
fs::create_directories(outpath.parent_path());
|
||||||
|
std::ofstream out(outpath, std::ios::binary);
|
||||||
|
out.write(reinterpret_cast<const char*>(filedata), filedata_len);
|
||||||
|
out.close();
|
||||||
|
// Set the file permissions
|
||||||
|
fs::permissions(outpath, file_perms);
|
||||||
|
|
||||||
|
if (!existed) {
|
||||||
|
std::cout << "[dehydrate] " << outpath.filename() << ": created\n";
|
||||||
|
} else {
|
||||||
|
std::cout << "[dehydrate] " << outpath.filename() << ": updated (hash changed)\n";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_permission_update) {
|
||||||
|
// Update only permissions
|
||||||
|
fs::permissions(outpath, file_perms);
|
||||||
|
std::cout << "[dehydrate] " << outpath.filename() << ": updated (permissions changed)\n";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool recreate_file(std::string destination_folder) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
fs::path outpath = fs::path(destination_folder) / "test1.txt";
|
||||||
|
|
||||||
|
// File data embedded as Base64
|
||||||
|
static const char filedata_base64[] = "VGhpcyBpcyBhIHNpbXBsZSB0ZXh0IGZpbGUuCkl0IGhhcyBtdWx0aXBsZSBsaW5lcy4KTGluZSAz"\
|
||||||
|
"Lg==";
|
||||||
|
|
||||||
|
// Decode Base64 data
|
||||||
|
size_t decoded_size = (strlen(filedata_base64) * 3) / 4;
|
||||||
|
unsigned char* decoded_data = new unsigned char[decoded_size];
|
||||||
|
size_t actual_size;
|
||||||
|
base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);
|
||||||
|
|
||||||
|
bool result = _recreate_file_(outpath, 11900461415522640014ULL, std::filesystem::perms(384), decoded_data, actual_size);
|
||||||
|
delete[] decoded_data;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
5
dehydrate/test/dehydrate_test_data/generated/_test1.hpp
Normal file
5
dehydrate/test/dehydrate_test_data/generated/_test1.hpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
namespace recreate_test1 {
|
||||||
|
bool recreate_file(std::string destination_folder);
|
||||||
|
}
|
1
dehydrate/test/dehydrate_test_data/original/small.txt
Normal file
1
dehydrate/test/dehydrate_test_data/original/small.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
x
|
@ -0,0 +1 @@
|
|||||||
|
This file is in a subdirectory.
|
3
dehydrate/test/dehydrate_test_data/original/test1.txt
Normal file
3
dehydrate/test/dehydrate_test_data/original/test1.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This is a simple text file.
|
||||||
|
It has multiple lines.
|
||||||
|
Line 3.
|
BIN
dehydrate/test/dehydrate_test_data/original/test2.bin
Executable file
BIN
dehydrate/test/dehydrate_test_data/original/test2.bin
Executable file
Binary file not shown.
3
dehydrate/test/dehydrate_test_data/original/test3.sh
Executable file
3
dehydrate/test/dehydrate_test_data/original/test3.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo 'Hello from test script'
|
||||||
|
exit 0
|
3
dehydrate/test/dehydrate_test_data/recreated/test1.txt
Normal file
3
dehydrate/test/dehydrate_test_data/recreated/test1.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This is a simple text file.
|
||||||
|
It has multiple lines.
|
||||||
|
Line 3.
|
@ -0,0 +1 @@
|
|||||||
|
x
|
@ -0,0 +1 @@
|
|||||||
|
This file is in a subdirectory.
|
@ -0,0 +1,3 @@
|
|||||||
|
This is a simple text file.
|
||||||
|
It has multiple lines.
|
||||||
|
Line 3.
|
BIN
dehydrate/test/dehydrate_test_data/recreated/tree/test2.bin
Executable file
BIN
dehydrate/test/dehydrate_test_data/recreated/tree/test2.bin
Executable file
Binary file not shown.
3
dehydrate/test/dehydrate_test_data/recreated/tree/test3.sh
Executable file
3
dehydrate/test/dehydrate_test_data/recreated/tree/test3.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo 'Hello from test script'
|
||||||
|
exit 0
|
BIN
dehydrate/test/dehydrate_test_data/test_recreate
Executable file
BIN
dehydrate/test/dehydrate_test_data/test_recreate
Executable file
Binary file not shown.
22
dehydrate/test/dehydrate_test_data/test_recreate.cpp
Normal file
22
dehydrate/test/dehydrate_test_data/test_recreate.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include "generated/_test1.hpp"
|
||||||
|
#include "generated/_original.hpp"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Testing file recreation..." << std::endl;
|
||||||
|
|
||||||
|
// Test single file recreation
|
||||||
|
std::cout << "Recreating single file..." << std::endl;
|
||||||
|
if (recreate_test1::recreate_file("recreated")) {
|
||||||
|
std::cout << "Single file recreation returned true" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test directory recreation
|
||||||
|
std::cout << "Recreating directory tree..." << std::endl;
|
||||||
|
if (recreate_original::recreate_tree("recreated/tree")) {
|
||||||
|
std::cout << "Directory recreation returned true" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
9
dehydrate/test/test.sh
Executable file
9
dehydrate/test/test.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple script to run the dehydrate tests
|
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
echo "Running dehydrate tests..."
|
||||||
|
./build_dehydrate_test.sh
|
Loading…
x
Reference in New Issue
Block a user