Skip to content

Commit 46cccbd

Browse files
committed
verify multi-day race doesn't break with new mapping
1 parent e37194d commit 46cccbd

2 files changed

Lines changed: 187 additions & 0 deletions

File tree

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ add_executable(solver_tests
4242
test_availability_overlap.cpp
4343
test_availability_boundaries.cpp
4444
test_validation.cpp
45+
test_long_race.cpp
4546
)
4647

4748
add_dependencies(solver_tests jres_solver_lib)

test/test_long_race.cpp

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#include "gtest/gtest.h"
2+
#include "jres_solver/jres_solver.hpp"
3+
#include "nlohmann/json.hpp"
4+
#include <vector>
5+
#include <string>
6+
#include <iomanip>
7+
#include <sstream>
8+
9+
using json = nlohmann::json;
10+
11+
// Helper to format ISO8601 time
12+
std::string format_time(int hour) {
13+
std::ostringstream oss;
14+
int day = 17 + (hour / 24);
15+
oss << "2026-01-" << std::setfill('0') << std::setw(2) << day << "T"
16+
<< std::setfill('0') << std::setw(2) << (hour % 24) << ":00:00";
17+
return oss.str();
18+
}
19+
20+
TEST(LongRaceTest, Solves48HourRace) {
21+
// Scenario: 4 Team Members, 48 Stints (1 hour each).
22+
// This tests if the solver can handle a larger problem size (longer duration).
23+
24+
json j;
25+
j["success"] = true;
26+
j["consecutiveStints"] = 2; // Encourage double stints
27+
j["minimumRestHours"] = 6; // Mandatory rest
28+
29+
json members = json::array();
30+
std::vector<std::string> names = {"Driver A", "Driver B", "Driver C", "Driver D"};
31+
for (const auto& name : names) {
32+
members.push_back({
33+
{"name", name},
34+
{"isDriver", true},
35+
{"isSpotter", true}
36+
});
37+
}
38+
j["teamMembers"] = members;
39+
40+
json stints = json::array();
41+
int num_stints = 48;
42+
for (int i = 0; i < num_stints; ++i) {
43+
stints.push_back({
44+
{"id", i + 1},
45+
{"startTime", format_time(i)},
46+
{"endTime", format_time(i + 1)}
47+
});
48+
}
49+
j["stints"] = stints;
50+
51+
// Everyone available all the time
52+
j["availability"] = json::object();
53+
for (const auto& name : names) {
54+
json member_avail = json::object();
55+
for (int i = 0; i < num_stints; ++i) {
56+
member_avail[format_time(i)] = "Available";
57+
}
58+
// Also add the end time of the last stint as an availability point?
59+
// The solver typically looks at stint start times or specific intervals.
60+
// Based on other tests, it seems to be keyed by time.
61+
// Let's just ensure we cover the stint start times.
62+
j["availability"][name] = member_avail;
63+
}
64+
65+
j["firstStintDriver"] = nullptr;
66+
67+
std::string json_str = j.dump();
68+
JresSolverInput* input = jres_input_from_json(json_str.c_str());
69+
ASSERT_NE(input, nullptr);
70+
71+
JresSolverOptions options = {};
72+
options.timeLimit = 30; // Give it a bit more time for a larger problem
73+
options.spotterMode = JRES_SPOTTER_MODE_INTEGRATED;
74+
options.allowNoSpotter = false;
75+
options.optimalityGap = 0.05; // Allow 5% gap to speed it up
76+
77+
JresSolverOutput* output = solve_race_schedule(input, &options);
78+
79+
// Check if we got a solution
80+
ASSERT_NE(output, nullptr);
81+
EXPECT_EQ(output->schedule_len, num_stints);
82+
EXPECT_EQ(output->diagnosis_len, 0);
83+
84+
// Basic validation: ensure no one drives > 4 hours in 6 hours (implicit check by solver, but good to know it solved)
85+
86+
free_jres_solver_output(output);
87+
free_jres_solver_input(input);
88+
}
89+
90+
TEST(LongRaceTest, DaySpecificAvailability) {
91+
// Scenario: Verify that availability at 10:00 Day 1 is distinct from 10:00 Day 2.
92+
// Stint 10 starts at 10:00 on Day 1.
93+
// Stint 34 starts at 10:00 on Day 2 (10 + 24 = 34).
94+
95+
// We will restrict availability such that:
96+
// - Stint 10 MUST be driven by Driver A (Driver B is unavailable).
97+
// - Stint 34 MUST be driven by Driver B (Driver A is unavailable).
98+
// If the solver conflates days, it might think Driver A is unavailable at Stint 10 (if Day 2 overwrites Day 1)
99+
// or Driver A is available at Stint 34 (if Day 1 overwrites Day 2).
100+
101+
json j;
102+
j["success"] = true;
103+
j["consecutiveStints"] = 1;
104+
j["minimumRestHours"] = 0;
105+
106+
json members = json::array();
107+
members.push_back({{"name", "Driver A"}, {"isDriver", true}, {"isSpotter", false}});
108+
members.push_back({{"name", "Driver B"}, {"isDriver", true}, {"isSpotter", false}});
109+
members.push_back({{"name", "Driver C"}, {"isDriver", true}, {"isSpotter", false}});
110+
j["teamMembers"] = members;
111+
112+
json stints = json::array();
113+
int num_stints = 48;
114+
for (int i = 0; i < num_stints; ++i) {
115+
stints.push_back({
116+
{"id", i + 1},
117+
{"startTime", format_time(i)},
118+
{"endTime", format_time(i + 1)}
119+
});
120+
}
121+
j["stints"] = stints;
122+
123+
j["availability"] = json::object();
124+
125+
// Default: Everyone available everywhere
126+
json avail_A = json::object();
127+
json avail_B = json::object();
128+
json avail_C = json::object();
129+
for (int i = 0; i < num_stints; ++i) {
130+
avail_A[format_time(i)] = "Available";
131+
avail_B[format_time(i)] = "Available";
132+
avail_C[format_time(i)] = "Available";
133+
}
134+
135+
// Constraint for Stint 10 (Hour 10)
136+
// Driver B and C are unavailable, so A must drive
137+
avail_B[format_time(10)] = "Unavailable";
138+
avail_C[format_time(10)] = "Unavailable";
139+
140+
// Constraint for Stint 34 (Hour 34 = 10 + 24)
141+
// Driver A and C are unavailable, so B must drive
142+
avail_A[format_time(34)] = "Unavailable";
143+
avail_C[format_time(34)] = "Unavailable";
144+
145+
j["availability"]["Driver A"] = avail_A;
146+
j["availability"]["Driver B"] = avail_B;
147+
j["availability"]["Driver C"] = avail_C;
148+
149+
j["firstStintDriver"] = nullptr;
150+
151+
std::string json_str = j.dump();
152+
JresSolverInput* input = jres_input_from_json(json_str.c_str());
153+
ASSERT_NE(input, nullptr);
154+
155+
JresSolverOptions options = {};
156+
options.timeLimit = 30;
157+
options.spotterMode = JRES_SPOTTER_MODE_NONE;
158+
options.allowNoSpotter = true; // No spotters needed for this test
159+
options.optimalityGap = 0.0;
160+
161+
JresSolverOutput* output = solve_race_schedule(input, &options);
162+
163+
ASSERT_NE(output, nullptr);
164+
if (output->diagnosis_len > 0) {
165+
for(int i=0; i<output->diagnosis_len; ++i) {
166+
std::cout << "Diagnosis: " << output->diagnosis[i] << std::endl;
167+
}
168+
}
169+
EXPECT_EQ(output->diagnosis_len, 0);
170+
EXPECT_EQ(output->schedule_len, num_stints);
171+
172+
// Verify Stint 10
173+
// schedule[10] corresponds to stint with id 11 (index 10)
174+
// startTime is format_time(10)
175+
EXPECT_STREQ(output->schedule[10].driver, "Driver A")
176+
<< "Stint 10 (Day 1 10:00) should be Driver A. Driver B was unavailable.";
177+
178+
// Verify Stint 34
179+
// schedule[34] corresponds to stint with id 35 (index 34)
180+
// startTime is format_time(34)
181+
EXPECT_STREQ(output->schedule[34].driver, "Driver B")
182+
<< "Stint 34 (Day 2 10:00) should be Driver B. Driver A was unavailable.";
183+
184+
free_jres_solver_output(output);
185+
free_jres_solver_input(input);
186+
}

0 commit comments

Comments
 (0)