Skip to content

Instantly share code, notes, and snippets.

@jfharden
Created December 8, 2023 09:33
Show Gist options
  • Save jfharden/e88912e7bebac3e243ed1ebe0172cfcc to your computer and use it in GitHub Desktop.
Save jfharden/e88912e7bebac3e243ed1ebe0172cfcc to your computer and use it in GitHub Desktop.
AoC 2023 day 5 part 2 unit tests
setup() {
load '../test_helper/bats-support/load' # this is required by bats-assert!
load '../test_helper/bats-assert/load'
# get the containing directory of this file
# use $BATS_TEST_FILENAME instead of ${BASH_SOURCE[0]} or $0,
# as those will point to the bats executable's location or the preprocessed file respectively
DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )"
# make executables in src/ visible to PATH
PATH="$DIR/../day5/:$PATH"
source "$DIR/../../day5/seed_maps_part2_optim.sh"
}
teardown() {
SEEDS_TO_PLANT=()
declare -gA seed_map=()
declare -gA soil_map=()
declare -gA fertilizer_map=()
declare -gA water_map=()
declare -gA light_map=()
declare -gA temperature_map=()
declare -gA humidity_map=()
INTERSECTING_RANGES=()
NEXT_STAGE_SOURCE_RANGES=()
NEXT_STAGE_DESTINATION_RANGES=()
SPLIT_RANGES=()
SPLIT_RANGES_INTERSECTING=()
SPLIT_RANGES_NON_INTERSECTING=()
SOURCE_RANGES=()
}
function array_contains {
# Check if an array contains a single element
#
# Args:
# $1: Element to look for
# $2..n: Array elements
for ELEMENT in "${@:2}"; do
if [ "$ELEMENT" == "$1" ]; then
return 0
fi
done
return 1
}
@test "input_file returns correct filenames" {
run input_file "real"
assert_output "input/1-real.txt"
run input_file "test"
assert_output "input/1-test.txt"
run input_file "test2"
assert_output "input/1-test2.txt"
}
@test "input_file fails with an invalid mode" {
run input_file "blah"
assert_failure
}
@test "parse_seeds_to_plant correctly sets SEEDS_TO_PLANT" {
parse_seeds_to_plant "seeds: 79 14 55 13"
assert_equal "${#SEEDS_TO_PLANT[@]}" "2"
assert_equal "${SEEDS_TO_PLANT[0]}" "79 14"
assert_equal "${SEEDS_TO_PLANT[1]}" "55 13"
SEEDS_TO_PLANT=()
parse_seeds_to_plant "seeds: 1848591090 462385043 2611025720 154883670 1508373603 11536371 3692308424 16905163 1203540561 280364121 3755585679 337861951 93589727 738327409 3421539474 257441906 3119409201 243224070 50985980 7961058"
assert_equal "${#SEEDS_TO_PLANT[@]}" "10"
assert_equal "${SEEDS_TO_PLANT[0]}" "1848591090 462385043"
assert_equal "${SEEDS_TO_PLANT[1]}" "2611025720 154883670"
assert_equal "${SEEDS_TO_PLANT[2]}" "1508373603 11536371"
assert_equal "${SEEDS_TO_PLANT[3]}" "3692308424 16905163"
assert_equal "${SEEDS_TO_PLANT[4]}" "1203540561 280364121"
assert_equal "${SEEDS_TO_PLANT[5]}" "3755585679 337861951"
assert_equal "${SEEDS_TO_PLANT[6]}" "93589727 738327409"
assert_equal "${SEEDS_TO_PLANT[7]}" "3421539474 257441906"
assert_equal "${SEEDS_TO_PLANT[8]}" "3119409201 243224070"
assert_equal "${SEEDS_TO_PLANT[9]}" "50985980 7961058"
}
@test "range_wholly_contained succeeds when range is wholly contained" {
run range_wholly_contained 5 10 1 20
assert_success
run range_wholly_contained 1 20 1 20
assert_success
run range_wholly_contained 5 20 1 20
assert_success
run range_wholly_contained 1 10 1 20
assert_success
}
@test "range_wholly_contained errors when range is not wholly contained" {
run range_wholly_contained 0 10 1 20
assert_failure
run range_wholly_contained 1 21 1 20
assert_failure
run range_wholly_contained 0 21 1 20
assert_failure
}
@test "parse_map_name outputs the map name correctly" {
run parse_map_name "seed-to-soil map:"
assert_output "seed"
run parse_map_name "soil-to-fertilizer map:"
assert_output "soil"
run parse_map_name "foo-to-bar map:"
assert_output "foo"
}
@test "copy_next_destination_to_source_ranges copies NEXT_STAGE_DESTINATION_RANGES" {
NEXT_STAGE_DESTINATION_RANGES=(
"1 10"
"31 41"
"1000 30000"
)
copy_next_destination_to_source_ranges
assert_equal "${#SOURCE_RANGES[@]}" 3
assert_equal "${SOURCE_RANGES[0]}" "1 10"
assert_equal "${SOURCE_RANGES[1]}" "31 41"
assert_equal "${SOURCE_RANGES[2]}" "1000 30000"
}
@test "copy_next_destination_to_source_ranges resets SOURCE_RANGES before copying" {
SOURCE_RANGES=("99 100")
NEXT_STAGE_DESTINATION_RANGES=(
"1 10"
"1000 30000"
)
copy_next_destination_to_source_ranges
assert_equal "${#SOURCE_RANGES[@]}" 2
assert_equal "${SOURCE_RANGES[0]}" "1 10"
assert_equal "${SOURCE_RANGES[1]}" "1000 30000"
}
@test "split_range sets 3 ranges when source completely overlaps" {
split_range 1 20 5 15
assert_equal ${#SPLIT_RANGES[@]} 3
assert_equal "${SPLIT_RANGES[0]}" "1 4"
assert_equal "${SPLIT_RANGES[1]}" "5 15"
assert_equal "${SPLIT_RANGES[2]}" "16 20"
}
@test "split_range sets 2 ranges when source overlaps the start only" {
split_range 1 15 5 15
assert_equal ${#SPLIT_RANGES[@]} 2
assert_equal "${SPLIT_RANGES[0]}" "1 4"
assert_equal "${SPLIT_RANGES[1]}" "5 15"
}
@test "split_range sets 2 ranges when source overlaps the end only" {
split_range 5 20 5 15
assert_equal ${#SPLIT_RANGES[@]} 2
assert_equal "${SPLIT_RANGES[0]}" "5 15"
assert_equal "${SPLIT_RANGES[1]}" "16 20"
}
@test "split_range_on_intersecting_ranges given a source range that overlaps 3 ranges will be split into 5" {
}
@test "split_range resets SPLIT_RANGES before assigning new ranges" {
SPLIT_RANGES=("100 200" "300 400")
split_range 1 20 5 15
assert_equal ${#SPLIT_RANGES[@]} 3
assert_equal "${SPLIT_RANGES[0]}" "1 4"
assert_equal "${SPLIT_RANGES[1]}" "5 15"
assert_equal "${SPLIT_RANGES[2]}" "16 20"
}
@test "build_ranges populates the map correctly" {
declare -gA fertilizer_map
LINES=(
"0 50 5"
"100 0 10"
"200 10 10"
"300 20 30"
)
build_ranges "fertilizer" "${LINES[@]}"
assert_equal "${#fertilizer_map[@]}" "4"
run array_contains "50 54" "${!fertilizer_map[@]}"
assert_success
assert_equal "${fertilizer_map["50 54"]}" "0 4"
run array_contains "0 9" "${!fertilizer_map[@]}"
assert_success
assert_equal "${fertilizer_map["0 9"]}" "100 109"
run array_contains "10 19" "${!fertilizer_map[@]}"
assert_success
assert_equal "${fertilizer_map["10 19"]}" "200 209"
run array_contains "20 49" "${!fertilizer_map[@]}"
assert_success
assert_equal "${fertilizer_map["20 49"]}" "300 329"
}
@test "get_all_intersecting_ranges sets a range that is wholly covering the source " {
declare -gA seed_map=(
["10 19"]="110 119"
["20 29"]="120 129"
["0 5"]="120 129"
)
get_all_intersecting_ranges 13 16 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 1
assert_equal "${INTERSECTING_RANGES[0]}" "10 19"
get_all_intersecting_ranges 10 15 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 1
assert_equal "${INTERSECTING_RANGES[0]}" "10 19"
get_all_intersecting_ranges 15 19 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 1
assert_equal "${INTERSECTING_RANGES[0]}" "10 19"
get_all_intersecting_ranges 10 19 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 1
assert_equal "${INTERSECTING_RANGES[0]}" "10 19"
}
@test "get_all_intersecting_ranges sets a range that covers only the start of the source" {
declare -gA seed_map=(
["10 19"]="110 119"
["20 29"]="120 129"
["0 5"]="120 129"
)
get_all_intersecting_ranges 8 10 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 1
assert_equal "${INTERSECTING_RANGES[0]}" "10 19"
get_all_intersecting_ranges 9 19 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 1
assert_equal "${INTERSECTING_RANGES[0]}" "10 19"
}
@test "get_all_intersecting_ranges sets a range that covers only the end of the source" {
declare -gA seed_map=(
["10 19"]="110 119"
["20 29"]="120 129"
["0 5"]="120 129"
)
get_all_intersecting_ranges 29 40 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 1
assert_equal "${INTERSECTING_RANGES[0]}" "20 29"
get_all_intersecting_ranges 20 40 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 1
assert_equal "${INTERSECTING_RANGES[0]}" "20 29"
}
@test "get_all_intersecting_ranges sets multipe ranges when the source covers multiple" {
declare -gA seed_map=(
["10 19"]="110 119"
["20 29"]="120 129"
["0 5"]="120 129"
)
get_all_intersecting_ranges 8 40 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 2
run array_contains "10 19" "${INTERSECTING_RANGES[@]}"
assert_success
run array_contains "20 29" "${INTERSECTING_RANGES[@]}"
assert_success
get_all_intersecting_ranges 3 15 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 2
run array_contains "0 5" "${INTERSECTING_RANGES[@]}"
assert_success
run array_contains "10 19" "${INTERSECTING_RANGES[@]}"
assert_success
}
@test "get_all_intersecting_ranges leaves INTERSECTING_RANGES empty if no ranges intersect" {
declare -gA seed_map=(
["10 19"]="110 119"
["30 39"]="120 129"
)
get_all_intersecting_ranges 900 999 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 0
get_all_intersecting_ranges 20 29 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 0
}
@test "get_all_intersecting_ranges clears INTERSECTING_RANGES before populating" {
INTERSECTING_RANGES=("900 999")
declare -gA seed_map=(
["10 19"]="110 119"
["20 29"]="120 129"
)
get_all_intersecting_ranges 15 19 "seed"
assert_equal "${#INTERSECTING_RANGES[@]}" 1
}
@test "get_destination_num returns the same number when there is no range in the map" {
declare -gA seed_map=(
["10 19"]="110 119"
["30 39"]="120 129"
)
assert_equal "$(get_destination_num 0 "seed")" 0
assert_equal "$(get_destination_num 100 "seed")" 100
assert_equal "$(get_destination_num 9 "seed")" 9
assert_equal "$(get_destination_num 20 "seed")" 20
}
@test "get_destination_num returns the correct destination number when there is a range map" {
declare -gA seed_map=(
["10 19"]="110 119"
["30 39"]="120 129"
["500 509"]="0 9"
["900 909"]="150 159"
)
assert_equal "$(get_destination_num 10 "seed")" 110
assert_equal "$(get_destination_num 14 "seed")" 114
assert_equal "$(get_destination_num 19 "seed")" 119
assert_equal "$(get_destination_num 30 "seed")" 120
assert_equal "$(get_destination_num 34 "seed")" 124
assert_equal "$(get_destination_num 39 "seed")" 129
assert_equal "$(get_destination_num 500 "seed")" 0
assert_equal "$(get_destination_num 505 "seed")" 5
assert_equal "$(get_destination_num 509 "seed")" 9
assert_equal "$(get_destination_num 900 "seed")" 150
assert_equal "$(get_destination_num 905 "seed")" 155
assert_equal "$(get_destination_num 909 "seed")" 159
}
@test "translate_ranges leaves ranges which are not mapped unchanged" {
declare -gA seed_map=(
["10 19"]="110 119"
["30 39"]="120 129"
["500 509"]="0 9"
["900 909"]="150 159"
)
NEXT_STAGE_SOURCE_RANGES=(
"15 19"
"20 25"
"900 909"
"501 508"
)
translate_ranges "seed"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 4
run array_contains "20 25" "${NEXT_STAGE_DESTINATION_RANGES[@]}"
assert_success
}
@test "translate_ranges translates ranges which are mapped" {
declare -gA seed_map=(
["10 19"]="110 119"
["30 39"]="120 129"
["500 509"]="0 9"
["900 909"]="150 159"
)
NEXT_STAGE_SOURCE_RANGES=(
"15 19"
"900 909"
"501 508"
)
translate_ranges "seed"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 3
run array_contains "115 119" "${NEXT_STAGE_DESTINATION_RANGES[@]}"
assert_success
run array_contains "150 159" "${NEXT_STAGE_DESTINATION_RANGES[@]}"
assert_success
run array_contains "1 8" "${NEXT_STAGE_DESTINATION_RANGES[@]}"
assert_success
}
@test "calculate_source_ranges populates NEXT_STAGE_DESTINATION_RANGES with ranges that have no intersections" {
declare -gA seed_map=(
["10 19"]="110 119"
["30 39"]="120 129"
["500 509"]="0 9"
["900 909"]="150 159"
)
calculate_source_ranges 0 9 "seed"
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 0
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 1
assert_equal "${NEXT_STAGE_DESTINATION_RANGES[0]}" "0 9"
NEXT_STAGE_DESTINATION_RANGES=()
NEXT_STAGE_SOURCE_RANGES=()
calculate_source_ranges 20 29 "seed"
echo "DEST ${NEXT_STAGE_DESTINATION_RANGES[@]}"
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 0
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 1
assert_equal "${NEXT_STAGE_DESTINATION_RANGES[0]}" "20 29"
NEXT_STAGE_DESTINATION_RANGES=()
NEXT_STAGE_SOURCE_RANGES=()
calculate_source_ranges 30000 40000 "seed"
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 0
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 1
assert_equal "${NEXT_STAGE_DESTINATION_RANGES[0]}" "30000 40000"
}
@test "calculate_source_ranges populates NEXT_STAGE_SOURCE_RANGES with ranges that are wholly within mapped ranges" {
declare -gA seed_map=(
["10 19"]="110 119"
["30 39"]="120 129"
["500 509"]="0 9"
["900 909"]="150 159"
)
calculate_source_ranges 10 19 "seed"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 0
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 1
assert_equal "${NEXT_STAGE_SOURCE_RANGES[0]}" "10 19"
NEXT_STAGE_SOURCE_RANGES=()
calculate_source_ranges 33 33 "seed"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 0
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 1
assert_equal "${NEXT_STAGE_SOURCE_RANGES[0]}" "33 33"
NEXT_STAGE_SOURCE_RANGES=()
calculate_source_ranges 501 509 "seed"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 0
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 1
assert_equal "${NEXT_STAGE_SOURCE_RANGES[0]}" "501 509"
}
@test "calculate_source_ranges populates NEXT_STAGE_SOURCE_RANGES and NEXT_STAGE_DESTINATION_RANGES from a source range that crosses multiple stage ranges" {
declare -gA seed_map=(
["10 19"]="110 119"
["30 39"]="120 129"
["500 509"]="0 9"
["900 909"]="150 159"
)
# expecting:
# 5-9 non
# 10-19 int
# 20-29 non
# 30-39 int
# 40-499 non
# 500-509 int
# 510-550 non
calculate_source_ranges 5 550 "seed"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 4
run array_contains "5 9" "${NEXT_STAGE_DESTINATION_RANGES[@]}"
assert_success
run array_contains "20 29" "${NEXT_STAGE_DESTINATION_RANGES[@]}"
assert_success
run array_contains "40 499" "${NEXT_STAGE_DESTINATION_RANGES[@]}"
assert_success
run array_contains "510 550" "${NEXT_STAGE_DESTINATION_RANGES[@]}"
assert_success
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 3
run array_contains "10 19" "${NEXT_STAGE_SOURCE_RANGES[@]}"
assert_success
run array_contains "30 39" "${NEXT_STAGE_SOURCE_RANGES[@]}"
assert_success
run array_contains "500 509" "${NEXT_STAGE_SOURCE_RANGES[@]}"
assert_success
}
@test "add_to_next_stage_ranges adds a range to NEXT_STAGE_SOURCE_RANGES when it's empty" {
add_to_next_stage_ranges 10 19 "SOURCE"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 0
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 1
assert_equal "${NEXT_STAGE_SOURCE_RANGES[0]}" "10 19"
}
@test "add_to_next_stage_ranges adds a range to NEXT_STAGE_SOURCE_RANGES which doesn't already have an entry" {
NEXT_STAGE_SOURCE_RANGES=("10 19")
add_to_next_stage_ranges 20 29 "SOURCE"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 0
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 2
assert_equal "${NEXT_STAGE_SOURCE_RANGES[0]}" "10 19"
assert_equal "${NEXT_STAGE_SOURCE_RANGES[1]}" "20 29"
}
@test "add_to_next_stage_ranges doesn't duplicate a range which is an exact duplicate of an already added range" {
NEXT_STAGE_SOURCE_RANGES=("10 19")
add_to_next_stage_ranges 10 19 "SOURCE"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 0
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 1
assert_equal "${NEXT_STAGE_SOURCE_RANGES[0]}" "10 19"
}
@test "add_to_next_stage_ranges adds a range to NEXT_STAGE_DESTINATION_RANGES when it's empty" {
add_to_next_stage_ranges 10 19 "DESTINATION"
assert_equal "${#NEXT_STAGE_DESTINATION_RANGES[@]}" 1
assert_equal "${NEXT_STAGE_DESTINATION_RANGES[0]}" "10 19"
assert_equal "${#NEXT_STAGE_SOURCE_RANGES[@]}" 0
}
@test "split_range_on_intersecting_ranges adds a range with no intersections to SPLIT_RANGES_NON_INTERSECTING" {
declare -gA seed_map=(
["10 19"]="110 119"
["25 29"]="125 129"
["50 69"]="150 169"
["90 95"]="190 195"
)
split_range_on_intersecting_ranges 0 9 "seed"
assert_equal "${#SPLIT_RANGES_NON_INTERSECTING[@]}" 1
assert_equal "${SPLIT_RANGES_NON_INTERSECTING[0]}" "0 9"
assert_equal "${#SPLIT_RANGES_INTERSECTING[@]}" 0
}
@test "split_range_on_intersecting_ranges when the range intersects only one range which wholly contains the source range adds the range to SPLIT_RANGES_INTERSECTING" {
declare -gA seed_map=(
["10 19"]="110 119"
["25 29"]="125 129"
["50 69"]="150 169"
["90 95"]="190 195"
)
split_range_on_intersecting_ranges 10 19 "seed"
assert_equal "${#SPLIT_RANGES_NON_INTERSECTING[@]}" 0
assert_equal "${#SPLIT_RANGES_INTERSECTING[@]}" 1
assert_equal "${SPLIT_RANGES_INTERSECTING[0]}" "10 19"
}
@test "split_range_on_intersecting_ranges when the range intersects only one range and needs to be split populates both SPLIT_RANGES_INTERSECTING and SPLIT_RANGES_NON_INTERSECTING" {
declare -gA seed_map=(
["10 19"]="110 119"
["25 29"]="125 129"
["50 69"]="150 169"
["90 95"]="190 195"
)
split_range_on_intersecting_ranges 5 15 "seed"
echo "${SPLIT_RANGES_INTERSECTING[@]}"
assert_equal "${#SPLIT_RANGES_INTERSECTING[@]}" 1
assert_equal "${SPLIT_RANGES_INTERSECTING[0]}" "10 15"
assert_equal "${#SPLIT_RANGES_NON_INTERSECTING[@]}" 1
assert_equal "${SPLIT_RANGES_NON_INTERSECTING[0]}" "5 9"
}
@test "split_range_on_intersecting_ranges when the range intersects multiple ranges with a gap populates both SPLIT_RANGES_INTERSECTING and SPLIT_RANGES_NON_INTERSECTING" {
declare -gA seed_map=(
["10 19"]="110 119"
["25 29"]="125 129"
["50 69"]="150 169"
["90 95"]="190 195"
)
split_range_on_intersecting_ranges 5 34 "seed"
assert_equal "${#SPLIT_RANGES_INTERSECTING[@]}" 2
run array_contains "10 19" "${SPLIT_RANGES_INTERSECTING[@]}"
assert_success
run array_contains "25 29" "${SPLIT_RANGES_INTERSECTING[@]}"
assert_success
assert_equal "${#SPLIT_RANGES_NON_INTERSECTING[@]}" 3
run array_contains "5 9" "${SPLIT_RANGES_NON_INTERSECTING[@]}"
assert_success
run array_contains "20 24" "${SPLIT_RANGES_NON_INTERSECTING[@]}"
assert_success
run array_contains "30 34" "${SPLIT_RANGES_NON_INTERSECTING[@]}"
assert_success
}
@test "split_range_on_intersecting_ranges when the range intersects multiple ranges with no gaps populates only SPLIT_RANGES_INTERSECTING" {
declare -gA seed_map=(
["10 19"]="110 119"
["20 29"]="125 129"
["50 69"]="150 169"
["90 95"]="190 195"
)
split_range_on_intersecting_ranges 10 25 "seed"
assert_equal "${#SPLIT_RANGES_INTERSECTING[@]}" 2
run array_contains "10 19" "${SPLIT_RANGES_INTERSECTING[@]}"
assert_success
run array_contains "20 25" "${SPLIT_RANGES_INTERSECTING[@]}"
assert_success
assert_equal "${#SPLIT_RANGES_NON_INTERSECTING[@]}" 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment