2015-02-17 17:28:49 +01:00
|
|
|
/*****************************************************************************
|
|
|
|
*
|
|
|
|
* This file is part of Mapnik (c++ mapping toolkit)
|
|
|
|
*
|
|
|
|
* Copyright (C) 2015 Artem Pavlenko
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
// stl
|
|
|
|
#include <algorithm>
|
|
|
|
#include <future>
|
2015-07-23 16:40:00 +02:00
|
|
|
#include <atomic>
|
2015-02-17 17:28:49 +01:00
|
|
|
|
|
|
|
#include <mapnik/load_map.hpp>
|
|
|
|
|
|
|
|
#include "runner.hpp"
|
|
|
|
|
|
|
|
namespace visual_tests
|
|
|
|
{
|
|
|
|
|
|
|
|
class renderer_visitor
|
|
|
|
{
|
|
|
|
public:
|
2015-06-30 14:25:35 +02:00
|
|
|
renderer_visitor(std::string const & name,
|
|
|
|
mapnik::Map & map,
|
|
|
|
map_size const & tiles,
|
|
|
|
double scale_factor,
|
|
|
|
result_list & results,
|
2015-07-07 14:35:28 +02:00
|
|
|
report_type & report,
|
2015-07-23 16:40:00 +02:00
|
|
|
std::size_t iterations,
|
|
|
|
bool is_fail_limit,
|
|
|
|
std::atomic<std::size_t> & fail_count)
|
2015-06-30 14:25:35 +02:00
|
|
|
: name_(name),
|
|
|
|
map_(map),
|
|
|
|
tiles_(tiles),
|
|
|
|
scale_factor_(scale_factor),
|
|
|
|
results_(results),
|
2015-07-07 14:35:28 +02:00
|
|
|
report_(report),
|
2015-07-23 16:40:00 +02:00
|
|
|
iterations_(iterations),
|
|
|
|
is_fail_limit_(is_fail_limit),
|
|
|
|
fail_count_(fail_count)
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-06-30 14:25:35 +02:00
|
|
|
template <typename T, typename std::enable_if<T::renderer_type::support_tiles>::type* = nullptr>
|
|
|
|
void operator()(T const & renderer)
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
2015-07-07 14:35:28 +02:00
|
|
|
test(renderer);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T, typename std::enable_if<!T::renderer_type::support_tiles>::type* = nullptr>
|
|
|
|
void operator()(T const & renderer)
|
|
|
|
{
|
2015-06-30 14:25:35 +02:00
|
|
|
if (tiles_.width == 1 && tiles_.height == 1)
|
|
|
|
{
|
2015-07-07 14:35:28 +02:00
|
|
|
test(renderer);
|
2015-06-30 14:25:35 +02:00
|
|
|
}
|
2015-07-07 14:35:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
template <typename T>
|
|
|
|
void test(T const & renderer)
|
|
|
|
{
|
|
|
|
map_size size { map_.width(), map_.height() };
|
|
|
|
std::chrono::high_resolution_clock::time_point start(std::chrono::high_resolution_clock::now());
|
|
|
|
for (std::size_t i = iterations_ ; i > 0; i--)
|
2015-06-30 14:25:35 +02:00
|
|
|
{
|
2015-07-07 14:35:28 +02:00
|
|
|
typename T::image_type image(render(renderer));
|
|
|
|
if (i == 1)
|
|
|
|
{
|
|
|
|
std::chrono::high_resolution_clock::time_point end(std::chrono::high_resolution_clock::now());
|
|
|
|
result r(renderer.report(image, name_, size, tiles_, scale_factor_));
|
|
|
|
r.duration = end - start;
|
|
|
|
mapnik::util::apply_visitor(report_visitor(r), report_);
|
|
|
|
results_.push_back(std::move(r));
|
2015-07-23 16:40:00 +02:00
|
|
|
if (is_fail_limit_ && r.state == STATE_FAIL)
|
|
|
|
{
|
|
|
|
++fail_count_;
|
|
|
|
}
|
2015-07-07 14:35:28 +02:00
|
|
|
}
|
2015-06-30 14:25:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-07 14:35:28 +02:00
|
|
|
template <typename T, typename std::enable_if<T::renderer_type::support_tiles>::type* = nullptr>
|
|
|
|
typename T::image_type render(T const & renderer)
|
2015-06-30 14:25:35 +02:00
|
|
|
{
|
|
|
|
if (tiles_.width == 1 && tiles_.height == 1)
|
|
|
|
{
|
2015-07-07 14:35:28 +02:00
|
|
|
return renderer.render(map_, scale_factor_);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return renderer.render(map_, scale_factor_, tiles_);
|
2015-06-30 14:25:35 +02:00
|
|
|
}
|
2015-02-17 17:28:49 +01:00
|
|
|
}
|
|
|
|
|
2015-07-07 14:35:28 +02:00
|
|
|
template <typename T, typename std::enable_if<!T::renderer_type::support_tiles>::type* = nullptr>
|
|
|
|
typename T::image_type render(T const & renderer)
|
|
|
|
{
|
|
|
|
return renderer.render(map_, scale_factor_);
|
|
|
|
}
|
|
|
|
|
2015-02-17 17:28:49 +01:00
|
|
|
std::string const & name_;
|
2015-06-30 14:25:35 +02:00
|
|
|
mapnik::Map & map_;
|
|
|
|
map_size const & tiles_;
|
2015-02-17 17:28:49 +01:00
|
|
|
double scale_factor_;
|
2015-06-30 14:25:35 +02:00
|
|
|
result_list & results_;
|
|
|
|
report_type & report_;
|
2015-07-07 14:35:28 +02:00
|
|
|
std::size_t iterations_;
|
2015-07-23 16:40:00 +02:00
|
|
|
bool is_fail_limit_;
|
|
|
|
std::atomic<std::size_t> & fail_count_;
|
2015-02-17 17:28:49 +01:00
|
|
|
};
|
|
|
|
|
2015-05-19 19:10:55 +02:00
|
|
|
runner::runner(runner::path_type const & styles_dir,
|
2015-07-07 14:35:28 +02:00
|
|
|
std::size_t iterations,
|
2015-07-23 16:40:00 +02:00
|
|
|
std::size_t fail_limit,
|
2015-09-16 13:24:14 +02:00
|
|
|
std::size_t jobs,
|
|
|
|
runner::renderer_container const & renderers)
|
2015-02-17 17:28:49 +01:00
|
|
|
: styles_dir_(styles_dir),
|
|
|
|
jobs_(jobs),
|
2015-07-07 14:35:28 +02:00
|
|
|
iterations_(iterations),
|
2015-07-23 16:40:00 +02:00
|
|
|
fail_limit_(fail_limit),
|
2015-09-16 13:24:14 +02:00
|
|
|
renderers_(renderers)
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
result_list runner::test_all(report_type & report) const
|
|
|
|
{
|
2015-05-19 19:10:55 +02:00
|
|
|
boost::filesystem::directory_iterator begin(styles_dir_);
|
|
|
|
boost::filesystem::directory_iterator end;
|
|
|
|
std::vector<runner::path_type> files(begin, end);
|
2015-05-12 04:04:23 +02:00
|
|
|
return test_parallel(files, report, jobs_);
|
2015-02-17 17:28:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
result_list runner::test(std::vector<std::string> const & style_names, report_type & report) const
|
|
|
|
{
|
2015-05-19 19:10:55 +02:00
|
|
|
std::vector<runner::path_type> files(style_names.size());
|
2015-02-17 17:28:49 +01:00
|
|
|
std::transform(style_names.begin(), style_names.end(), std::back_inserter(files),
|
2015-05-19 19:23:54 +02:00
|
|
|
[&](runner::path_type const & name)
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
2015-05-19 19:23:54 +02:00
|
|
|
return (name.extension() == ".xml") ? name : (styles_dir_ / (name.string() + ".xml"));
|
2015-02-17 17:28:49 +01:00
|
|
|
});
|
2015-05-12 04:04:23 +02:00
|
|
|
return test_parallel(files, report, jobs_);
|
2015-02-17 17:28:49 +01:00
|
|
|
}
|
|
|
|
|
2015-05-19 19:10:55 +02:00
|
|
|
result_list runner::test_parallel(std::vector<runner::path_type> const & files, report_type & report, std::size_t jobs) const
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
|
|
|
result_list results;
|
|
|
|
|
|
|
|
if (files.empty())
|
|
|
|
{
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (jobs == 0)
|
|
|
|
{
|
|
|
|
jobs = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::size_t chunk_size = files.size() / jobs;
|
|
|
|
|
|
|
|
if (chunk_size == 0)
|
|
|
|
{
|
|
|
|
chunk_size = files.size();
|
|
|
|
jobs = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::launch launch(jobs == 1 ? std::launch::deferred : std::launch::async);
|
|
|
|
std::vector<std::future<result_list>> futures(jobs);
|
2015-07-23 16:40:00 +02:00
|
|
|
std::atomic<std::size_t> fail_count(0);
|
2015-02-17 17:28:49 +01:00
|
|
|
|
|
|
|
for (std::size_t i = 0; i < jobs; i++)
|
|
|
|
{
|
|
|
|
files_iterator begin(files.begin() + i * chunk_size);
|
|
|
|
files_iterator end(files.begin() + (i + 1) * chunk_size);
|
|
|
|
|
|
|
|
// Handle remainder of files.size() / jobs
|
|
|
|
if (i == jobs - 1)
|
|
|
|
{
|
|
|
|
end = files.end();
|
|
|
|
}
|
|
|
|
|
2015-07-23 16:40:00 +02:00
|
|
|
futures[i] = std::async(launch, &runner::test_range, this, begin, end, std::ref(report), std::ref(fail_count));
|
2015-02-17 17:28:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (auto & f : futures)
|
|
|
|
{
|
|
|
|
result_list r = f.get();
|
|
|
|
std::move(r.begin(), r.end(), std::back_inserter(results));
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2015-07-23 16:40:00 +02:00
|
|
|
result_list runner::test_range(files_iterator begin,
|
|
|
|
files_iterator end,
|
|
|
|
std::reference_wrapper<report_type> report,
|
|
|
|
std::reference_wrapper<std::atomic<std::size_t>> fail_count) const
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
|
|
|
config defaults;
|
|
|
|
result_list results;
|
|
|
|
|
2015-05-19 19:10:55 +02:00
|
|
|
for (runner::files_iterator i = begin; i != end; i++)
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
2015-05-19 19:10:55 +02:00
|
|
|
runner::path_type const & file = *i;
|
|
|
|
if (file.extension() == ".xml")
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2015-07-23 16:40:00 +02:00
|
|
|
result_list r = test_one(file, defaults, report, fail_count.get());
|
2015-02-17 17:28:49 +01:00
|
|
|
std::move(r.begin(), r.end(), std::back_inserter(results));
|
|
|
|
}
|
|
|
|
catch (std::exception const& ex)
|
|
|
|
{
|
|
|
|
result r;
|
|
|
|
r.state = STATE_ERROR;
|
2015-05-19 19:10:55 +02:00
|
|
|
r.name = file.string();
|
2015-02-17 17:28:49 +01:00
|
|
|
r.error_message = ex.what();
|
2015-07-23 16:40:13 +02:00
|
|
|
r.duration = std::chrono::high_resolution_clock::duration::zero();
|
2015-02-17 17:28:49 +01:00
|
|
|
results.emplace_back(r);
|
|
|
|
mapnik::util::apply_visitor(report_visitor(r), report.get());
|
2015-07-23 16:40:00 +02:00
|
|
|
++fail_count.get();
|
2015-02-17 17:28:49 +01:00
|
|
|
}
|
|
|
|
}
|
2015-07-23 16:40:00 +02:00
|
|
|
if (fail_limit_ && fail_count.get() >= fail_limit_)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2015-02-17 17:28:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2015-07-23 16:40:00 +02:00
|
|
|
result_list runner::test_one(runner::path_type const& style_path,
|
|
|
|
config cfg,
|
|
|
|
report_type & report,
|
|
|
|
std::atomic<std::size_t> & fail_count) const
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
2015-06-30 14:25:35 +02:00
|
|
|
mapnik::Map map(cfg.sizes.front().width, cfg.sizes.front().height);
|
2015-02-17 17:28:49 +01:00
|
|
|
result_list results;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2015-06-30 14:25:35 +02:00
|
|
|
mapnik::load_map(map, style_path.string(), true);
|
2015-02-17 17:28:49 +01:00
|
|
|
}
|
|
|
|
catch (std::exception const& ex)
|
|
|
|
{
|
|
|
|
std::string what = ex.what();
|
|
|
|
if (what.find("Could not create datasource") != std::string::npos ||
|
|
|
|
what.find("Postgis Plugin: could not connect to server") != std::string::npos)
|
|
|
|
{
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
2015-06-30 14:25:35 +02:00
|
|
|
mapnik::parameters const & params = map.get_extra_parameters();
|
2015-02-17 17:28:49 +01:00
|
|
|
|
|
|
|
boost::optional<mapnik::value_integer> status = params.get<mapnik::value_integer>("status", cfg.status);
|
|
|
|
|
|
|
|
if (!*status)
|
|
|
|
{
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::optional<std::string> sizes = params.get<std::string>("sizes");
|
|
|
|
|
|
|
|
if (sizes)
|
|
|
|
{
|
|
|
|
cfg.sizes.clear();
|
|
|
|
parse_map_sizes(*sizes, cfg.sizes);
|
|
|
|
}
|
|
|
|
|
2015-06-30 14:25:35 +02:00
|
|
|
boost::optional<std::string> tiles = params.get<std::string>("tiles");
|
2015-02-17 17:28:49 +01:00
|
|
|
|
2015-06-30 14:25:35 +02:00
|
|
|
if (tiles)
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
2015-06-30 14:25:35 +02:00
|
|
|
cfg.tiles.clear();
|
|
|
|
parse_map_sizes(*tiles, cfg.tiles);
|
|
|
|
}
|
2015-02-17 17:28:49 +01:00
|
|
|
|
2015-06-30 14:25:35 +02:00
|
|
|
boost::optional<std::string> bbox_string = params.get<std::string>("bbox");
|
|
|
|
mapnik::box2d<double> box;
|
2015-02-17 17:28:49 +01:00
|
|
|
|
2015-06-30 14:25:35 +02:00
|
|
|
if (bbox_string)
|
|
|
|
{
|
|
|
|
box.from_string(*bbox_string);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string name(style_path.stem().string());
|
2015-02-17 17:28:49 +01:00
|
|
|
|
2015-06-30 14:25:35 +02:00
|
|
|
for (auto const & size : cfg.sizes)
|
|
|
|
{
|
|
|
|
for (auto const & scale_factor : cfg.scales)
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
2015-06-30 14:25:35 +02:00
|
|
|
for (auto const & tiles_count : cfg.tiles)
|
2015-02-17 17:28:49 +01:00
|
|
|
{
|
2015-06-30 14:25:35 +02:00
|
|
|
if (!tiles_count.width || !tiles_count.height)
|
|
|
|
{
|
|
|
|
throw std::runtime_error("Cannot render zero tiles.");
|
|
|
|
}
|
|
|
|
if (size.width % tiles_count.width || size.height % tiles_count.height)
|
|
|
|
{
|
|
|
|
throw std::runtime_error("Tile size is not an integer.");
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto const & ren : renderers_)
|
|
|
|
{
|
|
|
|
map.resize(size.width, size.height);
|
|
|
|
if (box.valid())
|
|
|
|
{
|
|
|
|
map.zoom_to_box(box);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
map.zoom_all();
|
|
|
|
}
|
2015-07-23 16:40:00 +02:00
|
|
|
mapnik::util::apply_visitor(renderer_visitor(name,
|
|
|
|
map,
|
|
|
|
tiles_count,
|
|
|
|
scale_factor,
|
|
|
|
results,
|
|
|
|
report,
|
|
|
|
iterations_,
|
|
|
|
fail_limit_,
|
|
|
|
fail_count), ren);
|
|
|
|
if (fail_limit_ && fail_count >= fail_limit_)
|
|
|
|
{
|
|
|
|
return results;
|
|
|
|
}
|
2015-06-30 14:25:35 +02:00
|
|
|
}
|
2015-02-17 17:28:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
void runner::parse_map_sizes(std::string const & str, std::vector<map_size> & sizes) const
|
|
|
|
{
|
|
|
|
boost::spirit::ascii::space_type space;
|
|
|
|
std::string::const_iterator iter = str.begin();
|
|
|
|
std::string::const_iterator end = str.end();
|
|
|
|
if (!boost::spirit::qi::phrase_parse(iter, end, map_sizes_parser_, space, sizes))
|
|
|
|
{
|
|
|
|
throw std::runtime_error("Failed to parse list of sizes: '" + str + "'");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|