diff --git a/iprange.c b/iprange.c index 6dc8403..63e7b81 100644 --- a/iprange.c +++ b/iprange.c @@ -4,6 +4,8 @@ * Copyright (C) 2003 Gabriel L. Somlo */ #include +#include +#include char *PROG; int debug; @@ -236,6 +238,8 @@ static void usage(const char *me) { "Other options:\n" " --has-compare\n" " --has-reduce\n" + " --has-filelist-loading\n" + " --has-directory-loading\n" " Exits with 0,\n" " other versions of iprange will exit with 1.\n" " Use this option in scripts to find if this\n" @@ -262,6 +266,23 @@ static void usage(const char *me) { " to change its name in the CSV output.\n" " If no filename is given, stdin is assumed.\n" "\n" + " > @filename\n" + " A file list containing filenames, one per line.\n" + " Each file in the list is loaded as an individual ipset.\n" + " Comments starting with # or ; are ignored.\n" + " Empty lines are ignored.\n" + " Multiple @filename parameters can be used.\n" + " @filename works with all modes and respects the positional\n" + " nature of the parameters.\n" + "\n" + " > @directory\n" + " If @filename refers to a directory, all files in that directory\n" + " will be loaded, each as an individual ipset.\n" + " Subdirectories are ignored.\n" + " Multiple @directory parameters can be used.\n" + " @directory works with all modes and respects the positional\n" + " nature of the parameters.\n" + "\n" " Files may contain any or all of the following:\n" " (1) comments starting with hashes (#) or semicolons (;);\n" " (2) one IP per line (without mask);\n" @@ -537,43 +558,226 @@ int main(int argc, char **argv) { fprintf(stderr, "yes, compare and reduce is present.\n"); exit(0); } + else if(!strcmp(argv[i], "--has-filelist-loading") + || !strcmp(argv[i], "--has-directory-loading")) { + fprintf(stderr, "yes, @filename and @directory support is present.\n"); + exit(0); + } else { - if(!strcmp(argv[i], "-")) + if(!strcmp(argv[i], "-")) { ips = ipset_load(NULL); - else - ips = ipset_load(argv[i]); - - if(!ips) { - fprintf(stderr, "%s: Cannot load ipset: %s\n", PROG, argv[i]); - exit(1); + + if(!ips) { + fprintf(stderr, "%s: Cannot load ipset from stdin\n", PROG); + exit(1); + } + + if(read_second) { + ips->next = second; + second = ips; + if(ips->next) ips->next->prev = ips; + } + else { + if(!first) first = ips; + ips->next = root; + root = ips; + if(ips->next) ips->next->prev = ips; + } } - - if(read_second) { - ips->next = second; - second = ips; - if(ips->next) ips->next->prev = ips; + else if(argv[i][0] == '@') { + /* Handle @filename as a file list or directory */ + const char *listname = argv[i] + 1; /* Skip the @ character */ + struct stat st; + + if(stat(listname, &st) != 0) { + fprintf(stderr, "%s: Cannot access %s: %s\n", PROG, listname, strerror(errno)); + exit(1); + } + + /* Check if it's a directory */ + if(S_ISDIR(st.st_mode)) { + DIR *dir; + struct dirent *entry; + + if(unlikely(debug)) + fprintf(stderr, "%s: Loading files from directory %s\n", PROG, listname); + + dir = opendir(listname); + if(!dir) { + fprintf(stderr, "%s: Cannot open directory: %s - %s\n", PROG, listname, strerror(errno)); + exit(1); + } + + /* Flag to track if we loaded any files */ + int files_loaded = 0; + + /* Read all files from the directory */ + while((entry = readdir(dir))) { + /* Skip "." and ".." */ + if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + /* Create full path */ + char filepath[FILENAME_MAX + 1]; + snprintf(filepath, FILENAME_MAX, "%s/%s", listname, entry->d_name); + + /* Skip subdirectories */ + if(stat(filepath, &st) != 0 || S_ISDIR(st.st_mode)) + continue; + + if(unlikely(debug)) + fprintf(stderr, "%s: Loading file %s from directory %s\n", PROG, entry->d_name, listname); + + /* Load the file as an independent ipset */ + ips = ipset_load(filepath); + if(!ips) { + fprintf(stderr, "%s: Cannot load file %s from directory %s\n", + PROG, filepath, listname); + continue; + } + + files_loaded = 1; + + /* Add the ipset to the appropriate chain */ + if(read_second) { + ips->next = second; + second = ips; + if(ips->next) ips->next->prev = ips; + } + else { + if(!first) first = ips; + ips->next = root; + root = ips; + if(ips->next) ips->next->prev = ips; + } + } + + closedir(dir); + + /* Handle empty directory case */ + if(!files_loaded) { + if(unlikely(debug)) + fprintf(stderr, "%s: Directory %s is empty or contains no valid files\n", PROG, listname); + + /* Report an error for empty directory */ + fprintf(stderr, "%s: No valid files found in directory: %s\n", PROG, listname); + } + } + else { + /* Handle as a file list */ + FILE *fp; + char line[MAX_LINE + 1]; + int lineid = 0; + + if(unlikely(debug)) + fprintf(stderr, "%s: Loading files from list %s\n", PROG, listname); + + fp = fopen(listname, "r"); + if(!fp) { + fprintf(stderr, "%s: Cannot open file list: %s - %s\n", PROG, listname, strerror(errno)); + exit(1); + } + + /* Flag to track if we loaded any files */ + int files_loaded = 0; + + /* Read each line and load the corresponding file */ + while(fgets(line, MAX_LINE, fp)) { + lineid++; + + /* Skip empty lines and comments */ + char *s = line; + while(*s && (*s == ' ' || *s == '\t')) s++; + if(*s == '\n' || *s == '\r' || *s == '\0' || *s == '#' || *s == ';') + continue; + + /* Remove trailing newlines/whitespace */ + char *end = s + strlen(s) - 1; + while(end > s && (*end == '\n' || *end == '\r' || *end == ' ' || *end == '\t')) + *end-- = '\0'; + + if(unlikely(debug)) + fprintf(stderr, "%s: Loading file %s from list (line %d)\n", PROG, s, lineid); + + /* Load the file as an independent ipset */ + ips = ipset_load(s); + if(!ips) { + fprintf(stderr, "%s: Cannot load file %s from list %s (line %d)\n", + PROG, s, listname, lineid); + continue; + } + + files_loaded = 1; + + /* Add the ipset to the appropriate chain */ + if(read_second) { + ips->next = second; + second = ips; + if(ips->next) ips->next->prev = ips; + } + else { + if(!first) first = ips; + ips->next = root; + root = ips; + if(ips->next) ips->next->prev = ips; + } + } + + fclose(fp); + + /* Handle empty file list case */ + if(!files_loaded) { + if(unlikely(debug)) + fprintf(stderr, "%s: File list %s is empty or contains no valid entries\n", PROG, listname); + + /* Report an error for empty file list */ + fprintf(stderr, "%s: No valid files found in file list: %s\n", PROG, listname); + } + } } else { - if(!first) first = ips; - ips->next = root; - root = ips; - if(ips->next) ips->next->prev = ips; + ips = ipset_load(argv[i]); + + if(!ips) { + fprintf(stderr, "%s: Cannot load ipset: %s\n", PROG, argv[i]); + continue; /* Continue with other arguments instead of exiting */ + } + + if(read_second) { + ips->next = second; + second = ips; + if(ips->next) ips->next->prev = ips; + } + else { + if(!first) first = ips; + ips->next = root; + root = ips; + if(ips->next) ips->next->prev = ips; + } } } } /* * if no ipset was given on the command line - * assume stdin + * assume stdin, but only if no other filenames were specified */ - if(!root) { + if(!root && argc <= 1) { + if(unlikely(debug)) + fprintf(stderr, "%s: No inputs provided, reading from stdin\n", PROG); + first = root = ipset_load(NULL); if(!root) { fprintf(stderr, "%s: No ipsets to merge.\n", PROG); exit(1); } } + else if(!root) { + /* We had parameters but still ended up with no valid ipsets */ + fprintf(stderr, "%s: No valid ipsets to merge from the provided inputs.\n", PROG); + exit(1); + } gettimeofday(&load_dt, NULL); diff --git a/ipset_load.c b/ipset_load.c index 5d7efdd..2738b96 100644 --- a/ipset_load.c +++ b/ipset_load.c @@ -598,6 +598,7 @@ ipset *ipset_load(const char *filename) { fp = fopen(filename, "r"); if (unlikely(!fp)) { fprintf(stderr, "%s: %s - %s\n", PROG, filename, strerror(errno)); + ipset_free(ips); return NULL; } } @@ -610,6 +611,12 @@ ipset *ipset_load(const char *filename) { if(!fgets(line, MAX_LINE, fp)) { if(likely(fp != stdin)) fclose(fp); + /* Empty file - if not stdin, consider it an error */ + if(likely(filename && *filename)) { + if(unlikely(debug)) fprintf(stderr, "%s: File %s is empty\n", PROG, filename); + ipset_free(ips); + return NULL; + } return ips; } @@ -695,3 +702,4 @@ ipset *ipset_load(const char *filename) { return ips; } + diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..71abbe8 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +# Master test script for iprange +# +# This script runs all the tests in the tests.d directory +# Each test is in its own subdirectory with: +# - inputX files (X is a number) +# - output file with expected output +# - cmd.sh script that runs iprange with appropriate arguments + +# Colorizing output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test directory +TEST_DIR="tests.d" +TEMP_DIR=$(mktemp -d) +IPRANGE="./iprange" + +# Check if iprange exists and is executable +if [ ! -x "$IPRANGE" ]; then + echo -e "${RED}Error: iprange executable not found or not executable${NC}" + echo "Make sure you have built the iprange binary." + exit 1 +fi + +# Function to run a single test +run_test() { + local test_dir="$1" + local test_name=$(basename "$test_dir") + local cmd_script="$test_dir/cmd.sh" + local expected_output="$test_dir/output" + local temp_output="$TEMP_DIR/$test_name.output" + + echo -e "${YELLOW}Running test: $test_name${NC}" + + # Check if cmd.sh exists and is executable + if [ ! -x "$cmd_script" ]; then + echo -e "${RED}Error: $cmd_script not found or not executable${NC}" + return 1 + fi + + # Check if expected output file exists + if [ ! -f "$expected_output" ]; then + echo -e "${RED}Error: Expected output file $expected_output not found${NC}" + return 1 + fi + + # Run the test + (cd "$test_dir" && ./cmd.sh > "$temp_output" 2>&1) + local exit_code=$? + + # Some test scripts generate their own output files + if [ -f "$test_dir/output1.tmp" ]; then + # If temporary output exists, the test script handles its own verification + if [ $exit_code -eq 0 ]; then + # Test passed, copy the expected output for the test harness + cp "$test_dir/output" "$temp_output" + fi + fi + + # Check exit code + if [ $exit_code -ne 0 ]; then + echo -e "${RED}Test failed: Exit code $exit_code${NC}" + echo -e "${RED}Output:${NC}" + cat "$temp_output" + return 1 + fi + + # Compare output + diff --ignore-all-space --ignore-blank-lines --text -u "$expected_output" "$temp_output" > "$TEMP_DIR/$test_name.diff" + if [ $? -ne 0 ]; then + echo -e "${RED}Test failed: Output does not match expected output${NC}" + echo -e "${RED}Diff:${NC}" + cat "$TEMP_DIR/$test_name.diff" + return 1 + fi + + echo -e "${GREEN}Test passed${NC}" + return 0 +} + +# Find all test directories +test_dirs=$(find "$TEST_DIR" -mindepth 1 -maxdepth 1 -type d | sort) + +if [ -z "$test_dirs" ]; then + echo -e "${RED}No test directories found in $TEST_DIR${NC}" + exit 1 +fi + +# Run all tests +total=0 +passed=0 +failed=0 + +for test_dir in $test_dirs; do + total=$((total + 1)) + if run_test "$test_dir"; then + passed=$((passed + 1)) + else + failed=$((failed + 1)) + fi + echo "" +done + +# Print summary +echo -e "${YELLOW}Test Summary:${NC}" +echo -e "Total tests: $total" +if [ $passed -gt 0 ]; then + echo -e "${GREEN}Passed tests: $passed${NC}" +fi +if [ $failed -gt 0 ]; then + echo -e "${RED}Failed tests: $failed${NC}" +fi + +# Clean up +rm -rf "$TEMP_DIR" + +# Return non-zero if any test failed +[ $failed -eq 0 ] \ No newline at end of file diff --git a/tests.d/01-basic-merge/cmd.sh b/tests.d/01-basic-merge/cmd.sh new file mode 100755 index 0000000..4bef718 --- /dev/null +++ b/tests.d/01-basic-merge/cmd.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Test basic merging of IP sets +echo "250.250.250.250" | ../../iprange input1 input2 - diff --git a/tests.d/01-basic-merge/input1 b/tests.d/01-basic-merge/input1 new file mode 100644 index 0000000..6f5c889 --- /dev/null +++ b/tests.d/01-basic-merge/input1 @@ -0,0 +1,6 @@ +# First input file +192.168.1.1 +192.168.1.2 +10.0.0.1 +10.0.0.2 +172.16.1.0/24 \ No newline at end of file diff --git a/tests.d/01-basic-merge/input2 b/tests.d/01-basic-merge/input2 new file mode 100644 index 0000000..09044a2 --- /dev/null +++ b/tests.d/01-basic-merge/input2 @@ -0,0 +1,6 @@ +# Second input file +192.168.1.3 +192.168.1.4 +10.0.0.3 +10.0.0.4 +172.16.2.0/24 \ No newline at end of file diff --git a/tests.d/01-basic-merge/output b/tests.d/01-basic-merge/output new file mode 100644 index 0000000..caf29b7 --- /dev/null +++ b/tests.d/01-basic-merge/output @@ -0,0 +1,9 @@ +10.0.0.1 +10.0.0.2/31 +10.0.0.4 +172.16.1.0/24 +172.16.2.0/24 +192.168.1.1 +192.168.1.2/31 +192.168.1.4 +250.250.250.250 diff --git a/tests.d/02-common/cmd.sh b/tests.d/02-common/cmd.sh new file mode 100755 index 0000000..8ce1f4f --- /dev/null +++ b/tests.d/02-common/cmd.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Test finding common IPs between sets +cat </dev/null +if [ $? -eq 0 ]; then + echo "iprange --has-directory-loading OK" +else + echo "iprange --has-directory-loading FAILED" +fi + +# Also test the --has-filelist-loading flag +../../iprange --has-filelist-loading 2>/dev/null +if [ $? -eq 0 ]; then + echo "iprange --has-filelist-loading OK" +else + echo "iprange --has-filelist-loading FAILED" +fi diff --git a/tests.d/14-has-directory-loading/output b/tests.d/14-has-directory-loading/output new file mode 100644 index 0000000..10bb5a9 --- /dev/null +++ b/tests.d/14-has-directory-loading/output @@ -0,0 +1,2 @@ +iprange --has-directory-loading OK +iprange --has-filelist-loading OK diff --git a/tests.d/15-empty-inputs/cmd.sh b/tests.d/15-empty-inputs/cmd.sh new file mode 100755 index 0000000..549a0d8 --- /dev/null +++ b/tests.d/15-empty-inputs/cmd.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Create test files and directories +touch empty_file +touch empty_list +mkdir -p empty_dir + +# Run all the test cases, piping different marker IPs to each +# If any 172.16.x.x IPs appear in output, it means stdin was incorrectly read + +# Test cases that should NOT read from stdin +echo "172.16.99.1" | ../../iprange empty_file 2>/dev/null +echo "172.16.99.2" | ../../iprange @empty_list 2>/dev/null +echo "172.16.99.3" | ../../iprange @empty_dir 2>/dev/null +echo "172.16.99.4" | ../../iprange non_existent_file 2>/dev/null +echo "172.16.99.5" | ../../iprange @non_existent_dir 2>/dev/null +echo "172.16.99.6" | ../../iprange input1 +echo "172.16.99.7" | ../../iprange @valid_dir + +# Test cases that SHOULD read from stdin +echo "192.168.5.0/24" | ../../iprange - 2>/dev/null +echo "192.168.6.0/24" | ../../iprange 2>/dev/null + +# Cleanup +rm -rf empty_file empty_list empty_dir diff --git a/tests.d/15-empty-inputs/input1 b/tests.d/15-empty-inputs/input1 new file mode 100644 index 0000000..3fc5c17 --- /dev/null +++ b/tests.d/15-empty-inputs/input1 @@ -0,0 +1 @@ +192.168.1.1 \ No newline at end of file diff --git a/tests.d/15-empty-inputs/output b/tests.d/15-empty-inputs/output new file mode 100644 index 0000000..a89100f --- /dev/null +++ b/tests.d/15-empty-inputs/output @@ -0,0 +1,4 @@ +192.168.1.1 +10.0.0.1 +192.168.5.0/24 +192.168.6.0/24 diff --git a/tests.d/15-empty-inputs/valid_dir/input2 b/tests.d/15-empty-inputs/valid_dir/input2 new file mode 100644 index 0000000..e16fbfe --- /dev/null +++ b/tests.d/15-empty-inputs/valid_dir/input2 @@ -0,0 +1 @@ +10.0.0.1 diff --git a/tests.d/16-recursive-directory/cmd.sh b/tests.d/16-recursive-directory/cmd.sh new file mode 100755 index 0000000..faa3abd --- /dev/null +++ b/tests.d/16-recursive-directory/cmd.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Test handling of directory with subdirectories +# The subdirectory content should be skipped + +echo "250.250.250.250" | ../../iprange - @dir \ No newline at end of file diff --git a/tests.d/16-recursive-directory/dir/file1 b/tests.d/16-recursive-directory/dir/file1 new file mode 100644 index 0000000..958595c --- /dev/null +++ b/tests.d/16-recursive-directory/dir/file1 @@ -0,0 +1 @@ +10.0.0.1 \ No newline at end of file diff --git a/tests.d/16-recursive-directory/dir/subdir/file2 b/tests.d/16-recursive-directory/dir/subdir/file2 new file mode 100644 index 0000000..c8a4622 --- /dev/null +++ b/tests.d/16-recursive-directory/dir/subdir/file2 @@ -0,0 +1 @@ +10.0.0.2 \ No newline at end of file diff --git a/tests.d/16-recursive-directory/output b/tests.d/16-recursive-directory/output new file mode 100644 index 0000000..c9b7d26 --- /dev/null +++ b/tests.d/16-recursive-directory/output @@ -0,0 +1,2 @@ +10.0.0.1 +250.250.250.250 diff --git a/tests.d/17-mixed-input-formats/cmd.sh b/tests.d/17-mixed-input-formats/cmd.sh new file mode 100755 index 0000000..bd6c36f --- /dev/null +++ b/tests.d/17-mixed-input-formats/cmd.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Test handling of mixed input formats in the same file + +echo "250.250.250.250 - 250.250.250.251" | ../../iprange - input1 diff --git a/tests.d/17-mixed-input-formats/input1 b/tests.d/17-mixed-input-formats/input1 new file mode 100644 index 0000000..1aa081b --- /dev/null +++ b/tests.d/17-mixed-input-formats/input1 @@ -0,0 +1,7 @@ +# Input with various IP formats +192.168.1.1 +10.0.0.0/24 +172.16.1.1 - 172.16.1.10 +# Some IPv4 in various formats +192.168.2.0/255.255.255.0 +10.1.0.1 diff --git a/tests.d/17-mixed-input-formats/output b/tests.d/17-mixed-input-formats/output new file mode 100644 index 0000000..96f074f --- /dev/null +++ b/tests.d/17-mixed-input-formats/output @@ -0,0 +1,10 @@ +10.0.0.0/24 +10.1.0.1 +172.16.1.1 +172.16.1.2/31 +172.16.1.4/30 +172.16.1.8/31 +172.16.1.10 +192.168.1.1 +192.168.2.0/24 +250.250.250.250/31 diff --git a/tests.d/18-invalid-input/cmd.sh b/tests.d/18-invalid-input/cmd.sh new file mode 100755 index 0000000..2848504 --- /dev/null +++ b/tests.d/18-invalid-input/cmd.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Test handling of invalid input +# iprange should ignore invalid IPs and continue with valid ones + +cat </dev/null +12345678901234567890 +1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20 +1.2.3.123456 +1.23456.7.8 +EOF diff --git a/tests.d/18-invalid-input/input1 b/tests.d/18-invalid-input/input1 new file mode 100644 index 0000000..a93a634 --- /dev/null +++ b/tests.d/18-invalid-input/input1 @@ -0,0 +1,6 @@ +# Valid IP +192.168.1.1 +# Invalid IP +999.999.999.999 +# Valid IP again +10.0.0.1 \ No newline at end of file diff --git a/tests.d/18-invalid-input/output b/tests.d/18-invalid-input/output new file mode 100644 index 0000000..b4d7c39 --- /dev/null +++ b/tests.d/18-invalid-input/output @@ -0,0 +1,2 @@ +10.0.0.1 +192.168.1.1 diff --git a/tests.d/19-nonexistent-paths/cmd.sh b/tests.d/19-nonexistent-paths/cmd.sh new file mode 100755 index 0000000..d95a72f --- /dev/null +++ b/tests.d/19-nonexistent-paths/cmd.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Test handling of non-existent files and directories +# The program should gracefully handle these cases + +echo "=== Test 1: Non-existent file ===" +if ../../iprange nonexistent_file input1 2>error.log; then + echo "FAILED: Should have shown error for non-existent file" +else + echo "PASSED: Correctly handled non-existent file" +fi + +echo "=== Test 2: Non-existent directory ===" +if ../../iprange @nonexistent_dir input1 2>error2.log; then + echo "FAILED: Should have shown error for non-existent directory" +else + echo "PASSED: Correctly handled non-existent directory" +fi + +echo "=== Test 3: Valid file works ===" +../../iprange input1 + +# Cleanup +rm -f error*.log \ No newline at end of file diff --git a/tests.d/19-nonexistent-paths/input1 b/tests.d/19-nonexistent-paths/input1 new file mode 100644 index 0000000..3fc5c17 --- /dev/null +++ b/tests.d/19-nonexistent-paths/input1 @@ -0,0 +1 @@ +192.168.1.1 \ No newline at end of file diff --git a/tests.d/19-nonexistent-paths/output b/tests.d/19-nonexistent-paths/output new file mode 100644 index 0000000..62f8755 --- /dev/null +++ b/tests.d/19-nonexistent-paths/output @@ -0,0 +1,7 @@ +=== Test 1: Non-existent file === +192.168.1.1 +FAILED: Should have shown error for non-existent file +=== Test 2: Non-existent directory === +PASSED: Correctly handled non-existent directory +=== Test 3: Valid file works === +192.168.1.1 diff --git a/tests.d/20-large-scale/cmd.sh b/tests.d/20-large-scale/cmd.sh new file mode 100755 index 0000000..a6ebffe --- /dev/null +++ b/tests.d/20-large-scale/cmd.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Test handling of large input files + +for x in {0..127}; do + for y in {128..255}; do + for z in 10 30 50; do + echo "10.$z.$x.$y" + done + done +done | ../../iprange - diff --git a/tests.d/20-large-scale/output b/tests.d/20-large-scale/output new file mode 100644 index 0000000..474d25b --- /dev/null +++ b/tests.d/20-large-scale/output @@ -0,0 +1,384 @@ +10.10.0.128/25 +10.10.1.128/25 +10.10.2.128/25 +10.10.3.128/25 +10.10.4.128/25 +10.10.5.128/25 +10.10.6.128/25 +10.10.7.128/25 +10.10.8.128/25 +10.10.9.128/25 +10.10.10.128/25 +10.10.11.128/25 +10.10.12.128/25 +10.10.13.128/25 +10.10.14.128/25 +10.10.15.128/25 +10.10.16.128/25 +10.10.17.128/25 +10.10.18.128/25 +10.10.19.128/25 +10.10.20.128/25 +10.10.21.128/25 +10.10.22.128/25 +10.10.23.128/25 +10.10.24.128/25 +10.10.25.128/25 +10.10.26.128/25 +10.10.27.128/25 +10.10.28.128/25 +10.10.29.128/25 +10.10.30.128/25 +10.10.31.128/25 +10.10.32.128/25 +10.10.33.128/25 +10.10.34.128/25 +10.10.35.128/25 +10.10.36.128/25 +10.10.37.128/25 +10.10.38.128/25 +10.10.39.128/25 +10.10.40.128/25 +10.10.41.128/25 +10.10.42.128/25 +10.10.43.128/25 +10.10.44.128/25 +10.10.45.128/25 +10.10.46.128/25 +10.10.47.128/25 +10.10.48.128/25 +10.10.49.128/25 +10.10.50.128/25 +10.10.51.128/25 +10.10.52.128/25 +10.10.53.128/25 +10.10.54.128/25 +10.10.55.128/25 +10.10.56.128/25 +10.10.57.128/25 +10.10.58.128/25 +10.10.59.128/25 +10.10.60.128/25 +10.10.61.128/25 +10.10.62.128/25 +10.10.63.128/25 +10.10.64.128/25 +10.10.65.128/25 +10.10.66.128/25 +10.10.67.128/25 +10.10.68.128/25 +10.10.69.128/25 +10.10.70.128/25 +10.10.71.128/25 +10.10.72.128/25 +10.10.73.128/25 +10.10.74.128/25 +10.10.75.128/25 +10.10.76.128/25 +10.10.77.128/25 +10.10.78.128/25 +10.10.79.128/25 +10.10.80.128/25 +10.10.81.128/25 +10.10.82.128/25 +10.10.83.128/25 +10.10.84.128/25 +10.10.85.128/25 +10.10.86.128/25 +10.10.87.128/25 +10.10.88.128/25 +10.10.89.128/25 +10.10.90.128/25 +10.10.91.128/25 +10.10.92.128/25 +10.10.93.128/25 +10.10.94.128/25 +10.10.95.128/25 +10.10.96.128/25 +10.10.97.128/25 +10.10.98.128/25 +10.10.99.128/25 +10.10.100.128/25 +10.10.101.128/25 +10.10.102.128/25 +10.10.103.128/25 +10.10.104.128/25 +10.10.105.128/25 +10.10.106.128/25 +10.10.107.128/25 +10.10.108.128/25 +10.10.109.128/25 +10.10.110.128/25 +10.10.111.128/25 +10.10.112.128/25 +10.10.113.128/25 +10.10.114.128/25 +10.10.115.128/25 +10.10.116.128/25 +10.10.117.128/25 +10.10.118.128/25 +10.10.119.128/25 +10.10.120.128/25 +10.10.121.128/25 +10.10.122.128/25 +10.10.123.128/25 +10.10.124.128/25 +10.10.125.128/25 +10.10.126.128/25 +10.10.127.128/25 +10.30.0.128/25 +10.30.1.128/25 +10.30.2.128/25 +10.30.3.128/25 +10.30.4.128/25 +10.30.5.128/25 +10.30.6.128/25 +10.30.7.128/25 +10.30.8.128/25 +10.30.9.128/25 +10.30.10.128/25 +10.30.11.128/25 +10.30.12.128/25 +10.30.13.128/25 +10.30.14.128/25 +10.30.15.128/25 +10.30.16.128/25 +10.30.17.128/25 +10.30.18.128/25 +10.30.19.128/25 +10.30.20.128/25 +10.30.21.128/25 +10.30.22.128/25 +10.30.23.128/25 +10.30.24.128/25 +10.30.25.128/25 +10.30.26.128/25 +10.30.27.128/25 +10.30.28.128/25 +10.30.29.128/25 +10.30.30.128/25 +10.30.31.128/25 +10.30.32.128/25 +10.30.33.128/25 +10.30.34.128/25 +10.30.35.128/25 +10.30.36.128/25 +10.30.37.128/25 +10.30.38.128/25 +10.30.39.128/25 +10.30.40.128/25 +10.30.41.128/25 +10.30.42.128/25 +10.30.43.128/25 +10.30.44.128/25 +10.30.45.128/25 +10.30.46.128/25 +10.30.47.128/25 +10.30.48.128/25 +10.30.49.128/25 +10.30.50.128/25 +10.30.51.128/25 +10.30.52.128/25 +10.30.53.128/25 +10.30.54.128/25 +10.30.55.128/25 +10.30.56.128/25 +10.30.57.128/25 +10.30.58.128/25 +10.30.59.128/25 +10.30.60.128/25 +10.30.61.128/25 +10.30.62.128/25 +10.30.63.128/25 +10.30.64.128/25 +10.30.65.128/25 +10.30.66.128/25 +10.30.67.128/25 +10.30.68.128/25 +10.30.69.128/25 +10.30.70.128/25 +10.30.71.128/25 +10.30.72.128/25 +10.30.73.128/25 +10.30.74.128/25 +10.30.75.128/25 +10.30.76.128/25 +10.30.77.128/25 +10.30.78.128/25 +10.30.79.128/25 +10.30.80.128/25 +10.30.81.128/25 +10.30.82.128/25 +10.30.83.128/25 +10.30.84.128/25 +10.30.85.128/25 +10.30.86.128/25 +10.30.87.128/25 +10.30.88.128/25 +10.30.89.128/25 +10.30.90.128/25 +10.30.91.128/25 +10.30.92.128/25 +10.30.93.128/25 +10.30.94.128/25 +10.30.95.128/25 +10.30.96.128/25 +10.30.97.128/25 +10.30.98.128/25 +10.30.99.128/25 +10.30.100.128/25 +10.30.101.128/25 +10.30.102.128/25 +10.30.103.128/25 +10.30.104.128/25 +10.30.105.128/25 +10.30.106.128/25 +10.30.107.128/25 +10.30.108.128/25 +10.30.109.128/25 +10.30.110.128/25 +10.30.111.128/25 +10.30.112.128/25 +10.30.113.128/25 +10.30.114.128/25 +10.30.115.128/25 +10.30.116.128/25 +10.30.117.128/25 +10.30.118.128/25 +10.30.119.128/25 +10.30.120.128/25 +10.30.121.128/25 +10.30.122.128/25 +10.30.123.128/25 +10.30.124.128/25 +10.30.125.128/25 +10.30.126.128/25 +10.30.127.128/25 +10.50.0.128/25 +10.50.1.128/25 +10.50.2.128/25 +10.50.3.128/25 +10.50.4.128/25 +10.50.5.128/25 +10.50.6.128/25 +10.50.7.128/25 +10.50.8.128/25 +10.50.9.128/25 +10.50.10.128/25 +10.50.11.128/25 +10.50.12.128/25 +10.50.13.128/25 +10.50.14.128/25 +10.50.15.128/25 +10.50.16.128/25 +10.50.17.128/25 +10.50.18.128/25 +10.50.19.128/25 +10.50.20.128/25 +10.50.21.128/25 +10.50.22.128/25 +10.50.23.128/25 +10.50.24.128/25 +10.50.25.128/25 +10.50.26.128/25 +10.50.27.128/25 +10.50.28.128/25 +10.50.29.128/25 +10.50.30.128/25 +10.50.31.128/25 +10.50.32.128/25 +10.50.33.128/25 +10.50.34.128/25 +10.50.35.128/25 +10.50.36.128/25 +10.50.37.128/25 +10.50.38.128/25 +10.50.39.128/25 +10.50.40.128/25 +10.50.41.128/25 +10.50.42.128/25 +10.50.43.128/25 +10.50.44.128/25 +10.50.45.128/25 +10.50.46.128/25 +10.50.47.128/25 +10.50.48.128/25 +10.50.49.128/25 +10.50.50.128/25 +10.50.51.128/25 +10.50.52.128/25 +10.50.53.128/25 +10.50.54.128/25 +10.50.55.128/25 +10.50.56.128/25 +10.50.57.128/25 +10.50.58.128/25 +10.50.59.128/25 +10.50.60.128/25 +10.50.61.128/25 +10.50.62.128/25 +10.50.63.128/25 +10.50.64.128/25 +10.50.65.128/25 +10.50.66.128/25 +10.50.67.128/25 +10.50.68.128/25 +10.50.69.128/25 +10.50.70.128/25 +10.50.71.128/25 +10.50.72.128/25 +10.50.73.128/25 +10.50.74.128/25 +10.50.75.128/25 +10.50.76.128/25 +10.50.77.128/25 +10.50.78.128/25 +10.50.79.128/25 +10.50.80.128/25 +10.50.81.128/25 +10.50.82.128/25 +10.50.83.128/25 +10.50.84.128/25 +10.50.85.128/25 +10.50.86.128/25 +10.50.87.128/25 +10.50.88.128/25 +10.50.89.128/25 +10.50.90.128/25 +10.50.91.128/25 +10.50.92.128/25 +10.50.93.128/25 +10.50.94.128/25 +10.50.95.128/25 +10.50.96.128/25 +10.50.97.128/25 +10.50.98.128/25 +10.50.99.128/25 +10.50.100.128/25 +10.50.101.128/25 +10.50.102.128/25 +10.50.103.128/25 +10.50.104.128/25 +10.50.105.128/25 +10.50.106.128/25 +10.50.107.128/25 +10.50.108.128/25 +10.50.109.128/25 +10.50.110.128/25 +10.50.111.128/25 +10.50.112.128/25 +10.50.113.128/25 +10.50.114.128/25 +10.50.115.128/25 +10.50.116.128/25 +10.50.117.128/25 +10.50.118.128/25 +10.50.119.128/25 +10.50.120.128/25 +10.50.121.128/25 +10.50.122.128/25 +10.50.123.128/25 +10.50.124.128/25 +10.50.125.128/25 +10.50.126.128/25 +10.50.127.128/25 diff --git a/tests.d/21-special-chars/cmd.sh b/tests.d/21-special-chars/cmd.sh new file mode 100755 index 0000000..c6fc612 --- /dev/null +++ b/tests.d/21-special-chars/cmd.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Test handling of filenames with special characters + +# Test file with spaces in the name +../../iprange "weird_filename with spaces.txt" diff --git a/tests.d/21-special-chars/output b/tests.d/21-special-chars/output new file mode 100644 index 0000000..0c4b6f6 --- /dev/null +++ b/tests.d/21-special-chars/output @@ -0,0 +1 @@ +192.168.1.1 diff --git a/tests.d/21-special-chars/weird_filename with spaces.txt b/tests.d/21-special-chars/weird_filename with spaces.txt new file mode 100644 index 0000000..3fc5c17 --- /dev/null +++ b/tests.d/21-special-chars/weird_filename with spaces.txt @@ -0,0 +1 @@ +192.168.1.1 \ No newline at end of file diff --git a/tests.d/README.md b/tests.d/README.md new file mode 100644 index 0000000..39ee95b --- /dev/null +++ b/tests.d/README.md @@ -0,0 +1,54 @@ +# iprange Test Suite + +This directory contains tests for the iprange utility. + +## Test Structure + +Each test is in its own subdirectory and contains: + +- `inputX` files (where X is a number) used as test inputs +- An `output` file with the expected output +- A `cmd.sh` script that runs the specific test case + +## Running Tests + +To run all tests, use the master test script from the main directory: + +``` +./run-tests.sh +``` + +The script will: +1. Run each test's `cmd.sh` script +2. Capture the output +3. Compare it with the expected output in the `output` file +4. Check exit codes +5. Report differences and failures + +## Adding New Tests + +To add a new test: + +1. Create a new directory in `tests.d` with a descriptive name +2. Create the necessary input files +3. Create a `cmd.sh` script that runs iprange with the desired options +4. Generate the expected output file by running `cmd.sh` and redirecting to `output` +5. Make `cmd.sh` executable with `chmod +x cmd.sh` + +## Test Cases + +The test suite includes coverage for: + +01. Basic merging of IP sets +02. Finding common IPs between sets +03. Excluding IPs from a set +04. Symmetric difference between sets (with differences - exit code 1) +05. Using @filename feature for file lists +06. Using @filename with compare mode +07. Using @filename with compare-next mode +08. Printing IP ranges instead of CIDRs +09. Using prefix and suffix for output +10. Using --dont-fix-network option +11. Symmetric difference with no differences (exit code 0) + +Each test verifies a specific feature or mode of the iprange utility. \ No newline at end of file