#include "Hardware/TDeck/MapTileDownloader.h" #include #include #include #include #include #include #include #include using namespace Hardware::TDeck; namespace { std::size_t tests_run = 0U; void fail(const char* expression, int line) { std::cerr << "line " << line << ": " << expression << '\n'; std::exit(1); } #define CHECK(e) do { if (!(e)) fail(#e, __LINE__); } while (false) void beginTest() { ++tests_run; } bool same(const TileKey& a, const TileKey& b) { return a.zoom == b.zoom && a.x == b.x && a.y == b.y; } class FakeClock : public MapTileDownloadClock { public: std::uint64_t value; FakeClock() : value(100U) {} virtual std::uint64_t nowMs() const { return value; } }; class FakeStore : public MapTileDownloadStore { public: std::uint32_t maximum; bool available; TileStoreResult begin_result; TileStoreResult write_result; TileStoreResult finish_result; bool short_write; bool open; int begins; int finishes; int aborts; std::vector chunks; std::vector bytes; FakeStore() : maximum(9000U), available(true), begin_result(TileStoreResult::OK), write_result(TileStoreResult::OK), finish_result(TileStoreResult::OK), short_write(false), open(false), begins(0), finishes(0), aborts(0) {} virtual bool isAvailable() const { return available; } virtual std::uint32_t maxTileBytes() const { return maximum; } virtual TileStoreResult beginPut(const TileKey&) { ++begins; if (begin_result == TileStoreResult::OK) open = true; return begin_result; } virtual TileStoreResult writePutChunk(const std::uint8_t* data, std::size_t size) { if (write_result != TileStoreResult::OK) return write_result; chunks.push_back(size); const std::size_t written = (short_write && size != 0U) ? size - 1U : size; bytes.insert(bytes.end(), data, data + written); return short_write ? TileStoreResult::IO_ERROR : TileStoreResult::OK; } virtual TileStoreResult finishPut() { ++finishes; if (finish_result == TileStoreResult::OK) open = false; return finish_result; } virtual void abortPut() { ++aborts; open = false; bytes.clear(); } }; class FakeTransport : public MapTileTransport { public: TileTransportResult start_result; TileTransportResult read_result; int status; std::int64_t length; const char* type; std::vector body; std::size_t position; std::size_t forced_chunk; int starts; int reads; int closes; std::string url; std::string agent; std::string ca; std::uint32_t connect_timeout; std::uint32_t read_timeout; FakeClock* clock; std::uint64_t advance_on_read; FakeTransport() : start_result(TileTransportResult::OK), read_result(TileTransportResult::OK), status(200), length(-1), type("image/png"), position(0U), forced_chunk(0U), starts(0), reads(0), closes(0), connect_timeout(0U), read_timeout(0U), clock(NULL), advance_on_read(0U) {} virtual TileTransportResult start(const char* u, const char* a, const char* c, std::uint32_t ct, std::uint32_t rt, TileHttpResponse& response) { ++starts; url = u == NULL ? "" : u; agent = a == NULL ? "" : a; ca = c == NULL ? "" : c; connect_timeout = ct; read_timeout = rt; position = 0U; response.status_code = status; response.content_length = length; response.content_type = type; return start_result; } virtual TileTransportResult read(std::uint8_t* output, std::size_t capacity, std::size_t& count, bool& eof) { ++reads; if (clock != NULL) clock->value += advance_on_read; if (read_result != TileTransportResult::OK) { count = 0U; eof = false; return read_result; } std::size_t amount = std::min(capacity, body.size() - position); if (forced_chunk != 0U) amount = std::min(amount, forced_chunk); if (amount != 0U) std::memcpy(output, &body[position], amount); position += amount; count = amount; eof = position == body.size(); return TileTransportResult::OK; } virtual void close() { ++closes; } }; MapTileDownloadConfig config(const char* endpoint = "https://tile.openstreetmap.org") { MapTileDownloadConfig c; c.endpoint = endpoint; c.ca_certificate = "TEST CA"; c.firmware_version = "1.2.3"; c.overall_timeout_ms = 10000U; c.connect_timeout_ms = 321U; c.read_timeout_ms = 654U; return c; } MapTileDownloadPolicy enabled() { MapTileDownloadPolicy p; p.enabled = true; return p; } TileKey key(std::uint32_t x = 1U) { TileKey k = {3U, x, 2U}; return k; } std::vector bytes(std::size_t n) { return std::vector(n, 42U); } void runUntilIdle(MapTileDownloader& d, FakeClock& clock, int limit = 40) { for (int i = 0; i < limit && d.isBusy(); ++i) { CHECK(d.pump() == MapTilePumpResult::PROGRESSED); ++clock.value; } CHECK(!d.isBusy()); } MapTileDownloadResult take(MapTileDownloader& d) { MapTileDownloadResult r; CHECK(d.takeResult(r)); return r; } void testDisabledByDefault() { beginTest(); FakeStore s; FakeTransport t; FakeClock c; MapTileDownloadPolicy p; MapTileDownloader d(s,t,c,p,config()); CHECK(!p.enabled); CHECK(d.enqueue(key(),7U)==MapTileEnqueueResult::POLICY_DISABLED); CHECK(d.queuedCount()==0U); CHECK(t.starts==0); } void testCanonicalUrlAndBounds() { beginTest(); char out[MapTileDownloader::URL_CAPACITY]; CHECK(MapTileDownloader::canonicalUrl("https://tile.openstreetmap.org/",TileKey{22U,4194303U,4194303U},out,sizeof(out))==MapTileUrlResult::OK); CHECK(std::string(out)=="https://tile.openstreetmap.org/22/4194303/4194303.png"); char tiny[12]; std::memset(tiny,'X',sizeof(tiny)); CHECK(MapTileDownloader::canonicalUrl("https://tile.openstreetmap.org",key(),tiny,sizeof(tiny))==MapTileUrlResult::TOO_LONG); CHECK(tiny[0]=='\0'); CHECK(MapTileDownloader::canonicalUrl("https://tile.openstreetmap.org",TileKey{23U,0U,0U},out,sizeof(out))==MapTileUrlResult::INVALID_KEY); CHECK(MapTileDownloader::canonicalUrl("http://tile.openstreetmap.org",key(),out,sizeof(out))==MapTileUrlResult::INVALID_ARGUMENT); CHECK(MapTileDownloader::canonicalUrl("https://user@tile.example",key(),out,sizeof(out))==MapTileUrlResult::INVALID_ARGUMENT); std::string huge("https://"); huge.append(MapTileDownloader::URL_CAPACITY,'a'); CHECK(MapTileDownloader::canonicalUrl(huge.c_str(),key(),out,sizeof(out))==MapTileUrlResult::TOO_LONG); } void testDedupeAndQueueFullNoEviction() { beginTest(); FakeStore s; FakeTransport t; FakeClock c; MapTileDownloader d(s,t,c,enabled(),config()); for(std::uint32_t i=0;i