print tracebacks and line numbers for Lua runtime errors (#6564)

* print tracebacks and line numbers for Lua runtime errors

* revert format changes

* update changelog with lua traceback, #6564

* revert using protected_function for old GetStringListFromFunction and source_function #6564

* add unit test for line numbers in tracebacks, #6564

* apply clang-format (#6564)

* remove unused test helper function, #6564

* suppress leaksanitizer warnings in extract-tests, #6564

When the extractor encounters a lua runtime error, some osmium objects are not freed. In production this doesn't matter because these errors bring down OSRM. In the tests we catch them to ensure they occur, and the leaksanitizer flags them.
This commit is contained in:
Matthew Wigginton Bhagat-Conway 2023-03-23 14:18:58 -04:00 committed by GitHub
parent 192d077ada
commit d6afe91d8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 906 additions and 20 deletions

View File

@ -18,6 +18,7 @@
- CHANGED: Make edge metrics strongly typed [#6420](https://github.com/Project-OSRM/osrm-backend/pull/6420)
- FIXED: Typo in file name src/util/timed_historgram.cpp -> src/util/timed_histogram.cpp [#6428](https://github.com/Project-OSRM/osrm-backend/issues/6428)
- CHANGED: Replace boost::string_ref with std::string_view [#6433](https://github.com/Project-OSRM/osrm-backend/pull/6433)
- ADDED: Print tracebacks for Lua runtime errors [#6564](https://github.com/Project-OSRM/osrm-backend/pull/6564)
- Routing:
- FIXED: Fix adding traffic signal penalties during compression [#6419](https://github.com/Project-OSRM/osrm-backend/pull/6419)
# 5.27.1

View File

@ -41,10 +41,10 @@ struct LuaScriptingContext final
bool has_way_function = false;
bool has_segment_function = false;
sol::function turn_function;
sol::function way_function;
sol::function node_function;
sol::function segment_function;
sol::protected_function turn_function;
sol::protected_function way_function;
sol::protected_function node_function;
sol::protected_function segment_function;
int api_version = 4;
sol::table profile_table;

View File

@ -6,3 +6,12 @@
# #1 0x7f7ae595d13e (/usr/lib/x86_64-linux-gnu/libtbb.so.2+0x2213e)
leak:libtbb.so
# The extract-tests leak some memory in the tests to confirm that
# lua errors print tracebacks.
# This appears to be because when these tests throw exceptions, the
# osmium objects being processed are not freed. In production this doesn't
# matter, as the exceptions bring down the entire osrm-extract process. In the
# tests, we catch the error to make sure it occurs, which is why the
# leaksanitizer flags it.
leak:extract-tests

View File

@ -91,6 +91,19 @@ struct to_lua_object : public boost::static_visitor<sol::object>
};
} // namespace
// Handle a lua error thrown in a protected function by printing the traceback and bubbling
// exception up to caller. Lua errors are generally unrecoverable, so this exception should not be
// caught but instead should terminate the process. The point of having this error handler rather
// than just using unprotected Lua functions which terminate the process automatically is that this
// function provides more useful error messages including Lua tracebacks and line numbers.
void handle_lua_error(sol::protected_function_result &luares)
{
sol::error luaerr = luares;
std::string msg = luaerr.what();
std::cerr << msg << std::endl;
throw util::exception("Lua error (see stderr for traceback)");
}
Sol2ScriptingEnvironment::Sol2ScriptingEnvironment(
const std::string &file_name,
const std::vector<boost::filesystem::path> &location_dependent_data_paths)
@ -550,10 +563,16 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
std::numeric_limits<TurnPenalty::value_type>::max());
// call initialize function
sol::function setup_function = function_table.value()["setup"];
sol::protected_function setup_function = function_table.value()["setup"];
if (!setup_function.valid())
throw util::exception("Profile must have an setup() function.");
sol::optional<sol::table> profile_table = setup_function();
auto setup_result = setup_function();
if (!setup_result.valid())
handle_lua_error(setup_result);
sol::optional<sol::table> profile_table = setup_result;
if (profile_table == sol::nullopt)
throw util::exception("Profile setup() must return a table.");
else
@ -1123,7 +1142,9 @@ void Sol2ScriptingEnvironment::ProcessTurn(ExtractionTurn &turn)
case 2:
if (context.has_turn_penalty_function)
{
context.turn_function(context.profile_table, std::ref(turn));
auto luares = context.turn_function(context.profile_table, std::ref(turn));
if (!luares.valid())
handle_lua_error(luares);
// Turn weight falls back to the duration value in deciseconds
// or uses the extracted unit-less weight value
@ -1138,7 +1159,9 @@ void Sol2ScriptingEnvironment::ProcessTurn(ExtractionTurn &turn)
case 1:
if (context.has_turn_penalty_function)
{
context.turn_function(std::ref(turn));
auto luares = context.turn_function(std::ref(turn));
if (!luares.valid())
handle_lua_error(luares);
// Turn weight falls back to the duration value in deciseconds
// or uses the extracted unit-less weight value
@ -1184,24 +1207,28 @@ void Sol2ScriptingEnvironment::ProcessSegment(ExtractionSegment &segment)
if (context.has_segment_function)
{
sol::protected_function_result luares;
switch (context.api_version)
{
case 4:
case 3:
case 2:
context.segment_function(context.profile_table, std::ref(segment));
luares = context.segment_function(context.profile_table, std::ref(segment));
break;
case 1:
context.segment_function(std::ref(segment));
luares = context.segment_function(std::ref(segment));
break;
case 0:
context.segment_function(std::ref(segment.source),
std::ref(segment.target),
segment.distance,
segment.duration);
luares = context.segment_function(std::ref(segment.source),
std::ref(segment.target),
segment.distance,
segment.duration);
segment.weight = segment.duration; // back-compatibility fallback to duration
break;
}
if (!luares.valid())
handle_lua_error(luares);
}
}
@ -1211,20 +1238,27 @@ void LuaScriptingContext::ProcessNode(const osmium::Node &node,
{
BOOST_ASSERT(state.lua_state() != nullptr);
sol::protected_function_result luares;
// TODO check for api version, make sure luares is always set
switch (api_version)
{
case 4:
case 3:
node_function(profile_table, std::cref(node), std::ref(result), std::cref(relations));
luares =
node_function(profile_table, std::cref(node), std::ref(result), std::cref(relations));
break;
case 2:
node_function(profile_table, std::cref(node), std::ref(result));
luares = node_function(profile_table, std::cref(node), std::ref(result));
break;
case 1:
case 0:
node_function(std::cref(node), std::ref(result));
luares = node_function(std::cref(node), std::ref(result));
break;
}
if (!luares.valid())
handle_lua_error(luares);
}
void LuaScriptingContext::ProcessWay(const osmium::Way &way,
@ -1233,20 +1267,27 @@ void LuaScriptingContext::ProcessWay(const osmium::Way &way,
{
BOOST_ASSERT(state.lua_state() != nullptr);
sol::protected_function_result luares;
// TODO check for api version, make sure luares is always set
switch (api_version)
{
case 4:
case 3:
way_function(profile_table, std::cref(way), std::ref(result), std::cref(relations));
luares =
way_function(profile_table, std::cref(way), std::ref(result), std::cref(relations));
break;
case 2:
way_function(profile_table, std::cref(way), std::ref(result));
luares = way_function(profile_table, std::cref(way), std::ref(result));
break;
case 1:
case 0:
way_function(std::cref(way), std::ref(result));
luares = way_function(std::cref(way), std::ref(result));
break;
}
if (!luares.valid())
handle_lua_error(luares);
}
} // namespace osrm::extractor

View File

@ -0,0 +1,140 @@
-- copy of testbot with process_node throwing a runtime error
api_version = 4
function setup()
return {
properties = {
continue_straight_at_waypoint = true,
max_speed_for_map_matching = 30/3.6, --km -> m/s
weight_name = 'duration',
process_call_tagless_node = false,
u_turn_penalty = 20,
traffic_light_penalty = 7, -- seconds
use_turn_restrictions = true
},
classes = {"motorway", "toll", "TooWords2"},
excludable = {
{["motorway"] = true},
{["toll"] = true},
{["motorway"] = true, ["toll"] = true}
},
default_speed = 24,
speeds = {
primary = 36,
secondary = 18,
tertiary = 12,
steps = 6
}
}
end
function process_node (profile, node, result)
if (2 < nil) then
print("2 is less than nil")
end
-- check if node is a traffic light
-- TODO: a way to set the penalty value
end
function process_way (profile, way, result)
local highway = way:get_value_by_key("highway")
local toll = way:get_value_by_key("toll")
local name = way:get_value_by_key("name")
local oneway = way:get_value_by_key("oneway")
local route = way:get_value_by_key("route")
local duration = way:get_value_by_key("duration")
local maxspeed = tonumber(way:get_value_by_key ( "maxspeed"))
local maxspeed_forward = tonumber(way:get_value_by_key( "maxspeed:forward"))
local maxspeed_backward = tonumber(way:get_value_by_key( "maxspeed:backward"))
local junction = way:get_value_by_key("junction")
if name then
result.name = name
end
result.forward_mode = mode.driving
result.backward_mode = mode.driving
if duration and durationIsValid(duration) then
result.duration = math.max( 1, parseDuration(duration) )
result.forward_mode = mode.route
result.backward_mode = mode.route
else
local speed_forw = profile.speeds[highway] or profile.default_speed
local speed_back = speed_forw
if highway == "river" then
local temp_speed = speed_forw
result.forward_mode = mode.river_down
result.backward_mode = mode.river_up
speed_forw = temp_speed*1.5
speed_back = temp_speed/1.5
elseif highway == "steps" then
result.forward_mode = mode.steps_down
result.backward_mode = mode.steps_up
end
if maxspeed_forward ~= nil and maxspeed_forward > 0 then
speed_forw = maxspeed_forward
else
if maxspeed ~= nil and maxspeed > 0 and speed_forw > maxspeed then
speed_forw = maxspeed
end
end
if maxspeed_backward ~= nil and maxspeed_backward > 0 then
speed_back = maxspeed_backward
else
if maxspeed ~=nil and maxspeed > 0 and speed_back > maxspeed then
speed_back = maxspeed
end
end
result.forward_speed = speed_forw
result.backward_speed = speed_back
end
if oneway == "no" or oneway == "0" or oneway == "false" then
-- nothing to do
elseif oneway == "-1" then
result.forward_mode = mode.inaccessible
elseif oneway == "yes" or oneway == "1" or oneway == "true" or junction == "roundabout" then
result.backward_mode = mode.inaccessible
end
if highway == 'motorway' then
result.forward_classes["motorway"] = true
result.backward_classes["motorway"] = true
end
if toll == "yes" then
result.forward_classes["toll"] = true
result.backward_classes["toll"] = true
end
if junction == 'roundabout' then
result.roundabout = true
end
end
function process_turn (profile, turn)
if turn.is_u_turn then
turn.duration = turn.duration + profile.properties.u_turn_penalty
turn.weight = turn.weight + profile.properties.u_turn_penalty
end
if turn.has_traffic_light then
turn.duration = turn.duration + profile.properties.traffic_light_penalty
end
end
return {
setup = setup,
process_way = process_way,
process_node = process_node,
process_turn = process_turn
}

View File

@ -0,0 +1,143 @@
-- A copy of testbot with process_segment throwing a runtime error
api_version = 4
function setup()
return {
properties = {
continue_straight_at_waypoint = true,
max_speed_for_map_matching = 30/3.6, --km -> m/s
weight_name = 'duration',
process_call_tagless_node = false,
u_turn_penalty = 20,
traffic_light_penalty = 7, -- seconds
use_turn_restrictions = true
},
classes = {"motorway", "toll", "TooWords2"},
excludable = {
{["motorway"] = true},
{["toll"] = true},
{["motorway"] = true, ["toll"] = true}
},
default_speed = 24,
speeds = {
primary = 36,
secondary = 18,
tertiary = 12,
steps = 6
}
}
end
function process_node (profile, node, result)
-- check if node is a traffic light
-- TODO: a way to set the penalty value
end
function process_way (profile, way, result)
local highway = way:get_value_by_key("highway")
local toll = way:get_value_by_key("toll")
local name = way:get_value_by_key("name")
local oneway = way:get_value_by_key("oneway")
local route = way:get_value_by_key("route")
local duration = way:get_value_by_key("duration")
local maxspeed = tonumber(way:get_value_by_key ( "maxspeed"))
local maxspeed_forward = tonumber(way:get_value_by_key( "maxspeed:forward"))
local maxspeed_backward = tonumber(way:get_value_by_key( "maxspeed:backward"))
local junction = way:get_value_by_key("junction")
if name then
result.name = name
end
result.forward_mode = mode.driving
result.backward_mode = mode.driving
if duration and durationIsValid(duration) then
result.duration = math.max( 1, parseDuration(duration) )
result.forward_mode = mode.route
result.backward_mode = mode.route
else
local speed_forw = profile.speeds[highway] or profile.default_speed
local speed_back = speed_forw
if highway == "river" then
local temp_speed = speed_forw
result.forward_mode = mode.river_down
result.backward_mode = mode.river_up
speed_forw = temp_speed*1.5
speed_back = temp_speed/1.5
elseif highway == "steps" then
result.forward_mode = mode.steps_down
result.backward_mode = mode.steps_up
end
if maxspeed_forward ~= nil and maxspeed_forward > 0 then
speed_forw = maxspeed_forward
else
if maxspeed ~= nil and maxspeed > 0 and speed_forw > maxspeed then
speed_forw = maxspeed
end
end
if maxspeed_backward ~= nil and maxspeed_backward > 0 then
speed_back = maxspeed_backward
else
if maxspeed ~=nil and maxspeed > 0 and speed_back > maxspeed then
speed_back = maxspeed
end
end
result.forward_speed = speed_forw
result.backward_speed = speed_back
end
if oneway == "no" or oneway == "0" or oneway == "false" then
-- nothing to do
elseif oneway == "-1" then
result.forward_mode = mode.inaccessible
elseif oneway == "yes" or oneway == "1" or oneway == "true" or junction == "roundabout" then
result.backward_mode = mode.inaccessible
end
if highway == 'motorway' then
result.forward_classes["motorway"] = true
result.backward_classes["motorway"] = true
end
if toll == "yes" then
result.forward_classes["toll"] = true
result.backward_classes["toll"] = true
end
if junction == 'roundabout' then
result.roundabout = true
end
end
function process_turn (profile, turn)
if turn.is_u_turn then
turn.duration = turn.duration + profile.properties.u_turn_penalty
turn.weight = turn.weight + profile.properties.u_turn_penalty
end
if turn.has_traffic_light then
turn.duration = turn.duration + profile.properties.traffic_light_penalty
end
end
function process_segment (profile, segment)
if (2 < nil) then
print("2 is less than nil")
end
end
return {
setup = setup,
process_way = process_way,
process_node = process_node,
process_turn = process_turn,
process_segment = process_segment
}

View File

@ -0,0 +1,140 @@
-- Copy of testbot profile, with setup throwing a runtime error
api_version = 4
function setup()
if (2 < nil) then -- arithmetic with nil should error
return {}
end
return {
properties = {
continue_straight_at_waypoint = true,
max_speed_for_map_matching = 30/3.6, --km -> m/s
weight_name = 'duration',
process_call_tagless_node = false,
u_turn_penalty = 20,
traffic_light_penalty = 7, -- seconds
use_turn_restrictions = true
},
classes = {"motorway", "toll", "TooWords2"},
excludable = {
{["motorway"] = true},
{["toll"] = true},
{["motorway"] = true, ["toll"] = true}
},
default_speed = 24,
speeds = {
primary = 36,
secondary = 18,
tertiary = 12,
steps = 6
}
}
end
function process_node (profile, node, result)
-- check if node is a traffic light
-- TODO: a way to set the penalty value
end
function process_way (profile, way, result)
local highway = way:get_value_by_key("highway")
local toll = way:get_value_by_key("toll")
local name = way:get_value_by_key("name")
local oneway = way:get_value_by_key("oneway")
local route = way:get_value_by_key("route")
local duration = way:get_value_by_key("duration")
local maxspeed = tonumber(way:get_value_by_key ( "maxspeed"))
local maxspeed_forward = tonumber(way:get_value_by_key( "maxspeed:forward"))
local maxspeed_backward = tonumber(way:get_value_by_key( "maxspeed:backward"))
local junction = way:get_value_by_key("junction")
if name then
result.name = name
end
result.forward_mode = mode.driving
result.backward_mode = mode.driving
if duration and durationIsValid(duration) then
result.duration = math.max( 1, parseDuration(duration) )
result.forward_mode = mode.route
result.backward_mode = mode.route
else
local speed_forw = profile.speeds[highway] or profile.default_speed
local speed_back = speed_forw
if highway == "river" then
local temp_speed = speed_forw
result.forward_mode = mode.river_down
result.backward_mode = mode.river_up
speed_forw = temp_speed*1.5
speed_back = temp_speed/1.5
elseif highway == "steps" then
result.forward_mode = mode.steps_down
result.backward_mode = mode.steps_up
end
if maxspeed_forward ~= nil and maxspeed_forward > 0 then
speed_forw = maxspeed_forward
else
if maxspeed ~= nil and maxspeed > 0 and speed_forw > maxspeed then
speed_forw = maxspeed
end
end
if maxspeed_backward ~= nil and maxspeed_backward > 0 then
speed_back = maxspeed_backward
else
if maxspeed ~=nil and maxspeed > 0 and speed_back > maxspeed then
speed_back = maxspeed
end
end
result.forward_speed = speed_forw
result.backward_speed = speed_back
end
if oneway == "no" or oneway == "0" or oneway == "false" then
-- nothing to do
elseif oneway == "-1" then
result.forward_mode = mode.inaccessible
elseif oneway == "yes" or oneway == "1" or oneway == "true" or junction == "roundabout" then
result.backward_mode = mode.inaccessible
end
if highway == 'motorway' then
result.forward_classes["motorway"] = true
result.backward_classes["motorway"] = true
end
if toll == "yes" then
result.forward_classes["toll"] = true
result.backward_classes["toll"] = true
end
if junction == 'roundabout' then
result.roundabout = true
end
end
function process_turn (profile, turn)
if turn.is_u_turn then
turn.duration = turn.duration + profile.properties.u_turn_penalty
turn.weight = turn.weight + profile.properties.u_turn_penalty
end
if turn.has_traffic_light then
turn.duration = turn.duration + profile.properties.traffic_light_penalty
end
end
return {
setup = setup,
process_way = process_way,
process_node = process_node,
process_turn = process_turn
}

View File

@ -0,0 +1,140 @@
-- a copy of testbot with process_turn throwing an error
api_version = 4
function setup()
return {
properties = {
continue_straight_at_waypoint = true,
max_speed_for_map_matching = 30/3.6, --km -> m/s
weight_name = 'duration',
process_call_tagless_node = false,
u_turn_penalty = 20,
traffic_light_penalty = 7, -- seconds
use_turn_restrictions = true
},
classes = {"motorway", "toll", "TooWords2"},
excludable = {
{["motorway"] = true},
{["toll"] = true},
{["motorway"] = true, ["toll"] = true}
},
default_speed = 24,
speeds = {
primary = 36,
secondary = 18,
tertiary = 12,
steps = 6
}
}
end
function process_node (profile, node, result)
-- check if node is a traffic light
-- TODO: a way to set the penalty value
end
function process_way (profile, way, result)
local highway = way:get_value_by_key("highway")
local toll = way:get_value_by_key("toll")
local name = way:get_value_by_key("name")
local oneway = way:get_value_by_key("oneway")
local route = way:get_value_by_key("route")
local duration = way:get_value_by_key("duration")
local maxspeed = tonumber(way:get_value_by_key ( "maxspeed"))
local maxspeed_forward = tonumber(way:get_value_by_key( "maxspeed:forward"))
local maxspeed_backward = tonumber(way:get_value_by_key( "maxspeed:backward"))
local junction = way:get_value_by_key("junction")
if name then
result.name = name
end
result.forward_mode = mode.driving
result.backward_mode = mode.driving
if duration and durationIsValid(duration) then
result.duration = math.max( 1, parseDuration(duration) )
result.forward_mode = mode.route
result.backward_mode = mode.route
else
local speed_forw = profile.speeds[highway] or profile.default_speed
local speed_back = speed_forw
if highway == "river" then
local temp_speed = speed_forw
result.forward_mode = mode.river_down
result.backward_mode = mode.river_up
speed_forw = temp_speed*1.5
speed_back = temp_speed/1.5
elseif highway == "steps" then
result.forward_mode = mode.steps_down
result.backward_mode = mode.steps_up
end
if maxspeed_forward ~= nil and maxspeed_forward > 0 then
speed_forw = maxspeed_forward
else
if maxspeed ~= nil and maxspeed > 0 and speed_forw > maxspeed then
speed_forw = maxspeed
end
end
if maxspeed_backward ~= nil and maxspeed_backward > 0 then
speed_back = maxspeed_backward
else
if maxspeed ~=nil and maxspeed > 0 and speed_back > maxspeed then
speed_back = maxspeed
end
end
result.forward_speed = speed_forw
result.backward_speed = speed_back
end
if oneway == "no" or oneway == "0" or oneway == "false" then
-- nothing to do
elseif oneway == "-1" then
result.forward_mode = mode.inaccessible
elseif oneway == "yes" or oneway == "1" or oneway == "true" or junction == "roundabout" then
result.backward_mode = mode.inaccessible
end
if highway == 'motorway' then
result.forward_classes["motorway"] = true
result.backward_classes["motorway"] = true
end
if toll == "yes" then
result.forward_classes["toll"] = true
result.backward_classes["toll"] = true
end
if junction == 'roundabout' then
result.roundabout = true
end
end
function process_turn (profile, turn)
if (2 < nil) then
print("2 is less than nil")
end
if turn.is_u_turn then
turn.duration = turn.duration + profile.properties.u_turn_penalty
turn.weight = turn.weight + profile.properties.u_turn_penalty
end
if turn.has_traffic_light then
turn.duration = turn.duration + profile.properties.traffic_light_penalty
end
end
return {
setup = setup,
process_way = process_way,
process_node = process_node,
process_turn = process_turn
}

View File

@ -0,0 +1,140 @@
-- copy of testbot with process_way throwing a runtime error
api_version = 4
function setup()
return {
properties = {
continue_straight_at_waypoint = true,
max_speed_for_map_matching = 30/3.6, --km -> m/s
weight_name = 'duration',
process_call_tagless_node = false,
u_turn_penalty = 20,
traffic_light_penalty = 7, -- seconds
use_turn_restrictions = true
},
classes = {"motorway", "toll", "TooWords2"},
excludable = {
{["motorway"] = true},
{["toll"] = true},
{["motorway"] = true, ["toll"] = true}
},
default_speed = 24,
speeds = {
primary = 36,
secondary = 18,
tertiary = 12,
steps = 6
}
}
end
function process_node (profile, node, result)
-- check if node is a traffic light
-- TODO: a way to set the penalty value
end
function process_way (profile, way, result)
if (2 < nil) then
print("2 less than nil")
end
local highway = way:get_value_by_key("highway")
local toll = way:get_value_by_key("toll")
local name = way:get_value_by_key("name")
local oneway = way:get_value_by_key("oneway")
local route = way:get_value_by_key("route")
local duration = way:get_value_by_key("duration")
local maxspeed = tonumber(way:get_value_by_key ( "maxspeed"))
local maxspeed_forward = tonumber(way:get_value_by_key( "maxspeed:forward"))
local maxspeed_backward = tonumber(way:get_value_by_key( "maxspeed:backward"))
local junction = way:get_value_by_key("junction")
if name then
result.name = name
end
result.forward_mode = mode.driving
result.backward_mode = mode.driving
if duration and durationIsValid(duration) then
result.duration = math.max( 1, parseDuration(duration) )
result.forward_mode = mode.route
result.backward_mode = mode.route
else
local speed_forw = profile.speeds[highway] or profile.default_speed
local speed_back = speed_forw
if highway == "river" then
local temp_speed = speed_forw
result.forward_mode = mode.river_down
result.backward_mode = mode.river_up
speed_forw = temp_speed*1.5
speed_back = temp_speed/1.5
elseif highway == "steps" then
result.forward_mode = mode.steps_down
result.backward_mode = mode.steps_up
end
if maxspeed_forward ~= nil and maxspeed_forward > 0 then
speed_forw = maxspeed_forward
else
if maxspeed ~= nil and maxspeed > 0 and speed_forw > maxspeed then
speed_forw = maxspeed
end
end
if maxspeed_backward ~= nil and maxspeed_backward > 0 then
speed_back = maxspeed_backward
else
if maxspeed ~=nil and maxspeed > 0 and speed_back > maxspeed then
speed_back = maxspeed
end
end
result.forward_speed = speed_forw
result.backward_speed = speed_back
end
if oneway == "no" or oneway == "0" or oneway == "false" then
-- nothing to do
elseif oneway == "-1" then
result.forward_mode = mode.inaccessible
elseif oneway == "yes" or oneway == "1" or oneway == "true" or junction == "roundabout" then
result.backward_mode = mode.inaccessible
end
if highway == 'motorway' then
result.forward_classes["motorway"] = true
result.backward_classes["motorway"] = true
end
if toll == "yes" then
result.forward_classes["toll"] = true
result.backward_classes["toll"] = true
end
if junction == 'roundabout' then
result.roundabout = true
end
end
function process_turn (profile, turn)
if turn.is_u_turn then
turn.duration = turn.duration + profile.properties.u_turn_penalty
turn.weight = turn.weight + profile.properties.u_turn_penalty
end
if turn.has_traffic_light then
turn.duration = turn.duration + profile.properties.traffic_light_penalty
end
end
return {
setup = setup,
process_way = process_way,
process_node = process_node,
process_turn = process_turn
}

View File

@ -1,10 +1,32 @@
#include <boost/test/unit_test.hpp>
#include "osrm/exception.hpp"
#include "osrm/extractor.hpp"
#include "osrm/extractor_config.hpp"
#include <boost/algorithm/string.hpp>
#include <thread>
// utility class to redirect stderr so we can test it
// inspired by https://stackoverflow.com/questions/5405016
class redirect_stderr
{
// constructor: accept a pointer to a buffer where stderr will be redirected
public:
redirect_stderr(std::streambuf *buf)
// store the original buffer for later (original buffer returned by rdbuf)
: old(std::cerr.rdbuf(buf))
{
}
// destructor: restore the original cerr, regardless of how this class gets destroyed
~redirect_stderr() { std::cerr.rdbuf(old); }
// place to store the buffer
private:
std::streambuf *old;
};
BOOST_AUTO_TEST_SUITE(library_extract)
BOOST_AUTO_TEST_CASE(test_extract_with_invalid_config)
@ -26,4 +48,114 @@ BOOST_AUTO_TEST_CASE(test_extract_with_valid_config)
BOOST_CHECK_NO_THROW(osrm::extract(config));
}
BOOST_AUTO_TEST_CASE(test_setup_runtime_error)
{
osrm::ExtractorConfig config;
config.input_path = OSRM_TEST_DATA_DIR "/monaco.osm.pbf";
config.UseDefaultOutputNames(OSRM_TEST_DATA_DIR "/monaco.osm.pbf");
config.profile_path = OSRM_TEST_DATA_DIR "/profiles/bad_setup.lua";
config.small_component_size = 1000;
config.requested_num_threads = std::thread::hardware_concurrency();
std::stringstream output;
{
redirect_stderr redir(output.rdbuf());
BOOST_CHECK_THROW(osrm::extract(config), osrm::util::exception);
}
// We just look for the line number, file name, and error message. This avoids portability
// issues since the output contains the full path to the file, which may change between systems
BOOST_CHECK(boost::algorithm::contains(output.str(),
"bad_setup.lua:6: attempt to compare number with nil"));
}
BOOST_AUTO_TEST_CASE(test_way_runtime_error)
{
osrm::ExtractorConfig config;
config.input_path = OSRM_TEST_DATA_DIR "/monaco.osm.pbf";
config.UseDefaultOutputNames(OSRM_TEST_DATA_DIR "/monaco.osm.pbf");
config.profile_path = OSRM_TEST_DATA_DIR "/profiles/bad_way.lua";
config.small_component_size = 1000;
config.requested_num_threads = std::thread::hardware_concurrency();
std::stringstream output;
{
redirect_stderr redir(output.rdbuf());
BOOST_CHECK_THROW(osrm::extract(config), osrm::util::exception);
}
// We just look for the line number, file name, and error message. This avoids portability
// issues since the output contains the full path to the file, which may change between systems
BOOST_CHECK(boost::algorithm::contains(output.str(),
"bad_way.lua:41: attempt to compare number with nil"));
}
BOOST_AUTO_TEST_CASE(test_node_runtime_error)
{
osrm::ExtractorConfig config;
config.input_path = OSRM_TEST_DATA_DIR "/monaco.osm.pbf";
config.UseDefaultOutputNames(OSRM_TEST_DATA_DIR "/monaco.osm.pbf");
config.profile_path = OSRM_TEST_DATA_DIR "/profiles/bad_node.lua";
config.small_component_size = 1000;
config.requested_num_threads = std::thread::hardware_concurrency();
std::stringstream output;
{
redirect_stderr redir(output.rdbuf());
BOOST_CHECK_THROW(osrm::extract(config), osrm::util::exception);
}
// We just look for the line number, file name, and error message. This avoids portability
// issues since the output contains the full path to the file, which may change between systems
BOOST_CHECK(boost::algorithm::contains(output.str(),
"bad_node.lua:36: attempt to compare number with nil"));
}
BOOST_AUTO_TEST_CASE(test_segment_runtime_error)
{
osrm::ExtractorConfig config;
config.input_path = OSRM_TEST_DATA_DIR "/monaco.osm.pbf";
config.UseDefaultOutputNames(OSRM_TEST_DATA_DIR "/monaco.osm.pbf");
config.profile_path = OSRM_TEST_DATA_DIR "/profiles/bad_segment.lua";
config.small_component_size = 1000;
config.requested_num_threads = std::thread::hardware_concurrency();
std::stringstream output;
{
redirect_stderr redir(output.rdbuf());
BOOST_CHECK_THROW(osrm::extract(config), osrm::util::exception);
}
// We just look for the line number, file name, and error message. This avoids portability
// issues since the output contains the full path to the file, which may change between systems
BOOST_CHECK(boost::algorithm::contains(
output.str(), "bad_segment.lua:132: attempt to compare number with nil"));
}
BOOST_AUTO_TEST_CASE(test_turn_runtime_error)
{
osrm::ExtractorConfig config;
config.input_path = OSRM_TEST_DATA_DIR "/monaco.osm.pbf";
config.UseDefaultOutputNames(OSRM_TEST_DATA_DIR "/monaco.osm.pbf");
config.profile_path = OSRM_TEST_DATA_DIR "/profiles/bad_turn.lua";
config.small_component_size = 1000;
config.requested_num_threads = std::thread::hardware_concurrency();
std::stringstream output;
{
redirect_stderr redir(output.rdbuf());
BOOST_CHECK_THROW(osrm::extract(config), osrm::util::exception);
}
// We just look for the line number, file name, and error message. This avoids portability
// issues since the output contains the full path to the file, which may change between systems
BOOST_CHECK(boost::algorithm::contains(output.str(),
"bad_turn.lua:122: attempt to compare number with nil"));
}
BOOST_AUTO_TEST_SUITE_END()