bf286eefa6
The controller and the WiFi driver each need a large aggregate allocation, which a live app sitting on garbage can deny - that is why a failed connect often succeeded on retry. Both bindings now collect before initializing, and the tree reserves its node and spec capacity so a build does not reallocate into a tight heap.
385 lines
12 KiB
C++
385 lines
12 KiB
C++
#pragma once
|
|
|
|
// Host doubles for every provider, deliberately dumb: they record what a
|
|
// binding asked for and hand back canned values, so a test asserts the
|
|
// marshalling rather than a device.
|
|
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <lua/providers.h>
|
|
|
|
namespace fake {
|
|
|
|
using namespace esp32lua;
|
|
|
|
struct Log : LogProvider {
|
|
LogLevel level = LogLevel::Debug;
|
|
std::string message;
|
|
void write(LogLevel nextLevel, const std::string& nextMessage) override {
|
|
level = nextLevel;
|
|
message = nextMessage;
|
|
}
|
|
};
|
|
|
|
struct Settings : SettingsProvider {
|
|
int32_t degrees = 0;
|
|
std::string tz = "UTC0";
|
|
int32_t rotation() const override { return degrees; }
|
|
Status setRotation(int32_t value) override {
|
|
degrees = value;
|
|
return Status::success();
|
|
}
|
|
std::string timezone() const override { return tz; }
|
|
Status setTimezone(const std::string& value) override {
|
|
tz = value;
|
|
return Status::success();
|
|
}
|
|
};
|
|
|
|
struct Sys : SysProvider {
|
|
int32_t millis() const override { return 1234; }
|
|
MemoryInfo memory() const override {
|
|
const MemoryInfo info = {100, 200, 50};
|
|
return info;
|
|
}
|
|
bool isClockSynced() const override { return true; }
|
|
};
|
|
|
|
struct Fs : FsProvider {
|
|
std::map<std::string, std::string> files;
|
|
std::string written;
|
|
|
|
// One buffer handed to the runtime, which owns and deletes it.
|
|
struct Reader : FileReader {
|
|
std::string content;
|
|
size_t at = 0;
|
|
int32_t read(char* out, int32_t maxBytes) override {
|
|
const size_t remaining = content.size() - at;
|
|
const size_t count = remaining < static_cast<size_t>(maxBytes)
|
|
? remaining
|
|
: static_cast<size_t>(maxBytes);
|
|
content.copy(out, count, at);
|
|
at += count;
|
|
return static_cast<int32_t>(count);
|
|
}
|
|
};
|
|
|
|
FileReader* openRead(const std::string& path) override {
|
|
const std::map<std::string, std::string>::const_iterator found =
|
|
files.find(path);
|
|
if (found == files.end())
|
|
return nullptr;
|
|
Reader* reader = new Reader();
|
|
reader->content = found->second;
|
|
return reader;
|
|
}
|
|
|
|
bool exists(const std::string& path) const override {
|
|
return files.count(path) != 0;
|
|
}
|
|
Status fileSize(const std::string& path, int32_t& size) const override {
|
|
const std::map<std::string, std::string>::const_iterator found =
|
|
files.find(path);
|
|
if (found == files.end())
|
|
return Status::failure("no such file");
|
|
size = static_cast<int32_t>(found->second.size());
|
|
return Status::success();
|
|
}
|
|
Status listDirs(const std::string&,
|
|
std::vector<std::string>& names) const override {
|
|
names.push_back("apps");
|
|
return Status::success();
|
|
}
|
|
Status listFiles(const std::string&,
|
|
std::vector<std::string>& names) const override {
|
|
names.push_back("main.lua");
|
|
return Status::success();
|
|
}
|
|
Status mkdir(const std::string&) override { return Status::success(); }
|
|
Status readFile(const std::string& path, int32_t maxBytes,
|
|
std::string& content) const override {
|
|
const std::map<std::string, std::string>::const_iterator found =
|
|
files.find(path);
|
|
if (found == files.end())
|
|
return Status::failure("no such file");
|
|
if (static_cast<int32_t>(found->second.size()) > maxBytes)
|
|
return Status::failure("too large");
|
|
content = found->second;
|
|
return Status::success();
|
|
}
|
|
Status readLineAt(const std::string& path, int32_t offset, int32_t,
|
|
bool& found, std::string& line,
|
|
int32_t& nextOffset) const override {
|
|
const std::map<std::string, std::string>::const_iterator file =
|
|
files.find(path);
|
|
if (file == files.end())
|
|
return Status::failure("no such file");
|
|
if (offset >= static_cast<int32_t>(file->second.size())) {
|
|
found = false;
|
|
return Status::success();
|
|
}
|
|
const size_t end = file->second.find('\n', offset);
|
|
line = file->second.substr(
|
|
offset, end == std::string::npos ? std::string::npos : end - offset);
|
|
nextOffset = end == std::string::npos
|
|
? static_cast<int32_t>(file->second.size())
|
|
: static_cast<int32_t>(end + 1);
|
|
found = true;
|
|
return Status::success();
|
|
}
|
|
Status remove(const std::string& path) override {
|
|
files.erase(path);
|
|
return Status::success();
|
|
}
|
|
Status removeTree(const std::string&) override { return Status::success(); }
|
|
Status rename(const std::string&, const std::string&) override {
|
|
return Status::success();
|
|
}
|
|
Status writeFile(const std::string& path,
|
|
const std::string& content) override {
|
|
files[path] = content;
|
|
written = content;
|
|
return Status::success();
|
|
}
|
|
};
|
|
|
|
struct Gui : GuiProvider {
|
|
std::string trace;
|
|
int32_t degrees = 0;
|
|
bool fullscreen = false;
|
|
bool gradient = false;
|
|
|
|
FontIds fonts() const override {
|
|
const FontIds ids = {1, 2, 3, 4, 0, 1};
|
|
return ids;
|
|
}
|
|
int32_t width() const override { return 320; }
|
|
int32_t height() const override { return 240; }
|
|
int32_t rotation() const override { return degrees; }
|
|
void setRotation(int32_t value) override { degrees = value; }
|
|
int32_t color(int32_t r, int32_t g, int32_t b) const override {
|
|
return (r << 16) | (g << 8) | b;
|
|
}
|
|
void clear(int32_t) override { trace += "clear;"; }
|
|
void fillRect(int32_t, int32_t, int32_t, int32_t, int32_t) override {
|
|
trace += "fillRect;";
|
|
}
|
|
void drawRect(int32_t, int32_t, int32_t, int32_t, int32_t) override {
|
|
trace += "drawRect;";
|
|
}
|
|
void drawLine(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) override {
|
|
trace += "drawLine;";
|
|
}
|
|
void drawPixel(int32_t, int32_t, int32_t) override { trace += "drawPixel;"; }
|
|
void drawCircle(int32_t, int32_t, int32_t, int32_t, int32_t) override {
|
|
trace += "drawCircle;";
|
|
}
|
|
void fillCircle(int32_t, int32_t, int32_t, int32_t, const int32_t*) override {
|
|
trace += "fillCircle;";
|
|
}
|
|
void roundRect(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t,
|
|
const int32_t* top, const int32_t* bottom,
|
|
const int32_t* border) override {
|
|
if (top && bottom && *top != *bottom)
|
|
gradient = true;
|
|
trace += "roundRect(";
|
|
trace += top ? "fill" : "-";
|
|
trace += border ? ",border" : ",-";
|
|
trace += ");";
|
|
}
|
|
void setFullscreen(bool on) override { fullscreen = on; }
|
|
// Stands in for an e-ink panel, where a second commit is a second visible
|
|
// refresh.
|
|
void commit() override { commits++; }
|
|
int commits = 0;
|
|
void fillPolygon(const int32_t*, const int32_t*, size_t count,
|
|
int32_t) override {
|
|
trace += "fillPolygon" + std::to_string(count) + ";";
|
|
}
|
|
Status drawBmp(const std::string&, const int32_t*, const int32_t*,
|
|
const int32_t*, const int32_t*) override {
|
|
trace += "drawBmp;";
|
|
return Status::success();
|
|
}
|
|
int32_t textWidth(int32_t, const std::string& text, int32_t) const override {
|
|
return static_cast<int32_t>(text.size()) * 8;
|
|
}
|
|
int32_t fontHeight(int32_t, int32_t) const override { return 16; }
|
|
void drawText(int32_t, int32_t, int32_t, const std::string& text, int32_t,
|
|
int32_t, const int32_t*) override {
|
|
trace += "drawText(" + text + ");";
|
|
}
|
|
};
|
|
|
|
struct Http : HttpProvider {
|
|
std::string method;
|
|
std::string body;
|
|
std::vector<HttpHeader> headers;
|
|
int32_t limit = 0;
|
|
|
|
Status request(const std::string& nextMethod, const std::string&,
|
|
const std::string& nextBody,
|
|
const std::vector<HttpHeader>& nextHeaders, int32_t maxBytes,
|
|
HttpResponse& response) override {
|
|
method = nextMethod;
|
|
body = nextBody;
|
|
headers = nextHeaders;
|
|
limit = maxBytes;
|
|
response.status = 404;
|
|
response.body = "missing";
|
|
return Status::success();
|
|
}
|
|
Status download(const std::string&, const std::string&,
|
|
const HttpDownload& options, int32_t& bytesWritten) override {
|
|
bytesWritten = options.maxBytes;
|
|
return Status::success();
|
|
}
|
|
};
|
|
|
|
struct Timer : TimerProvider {
|
|
std::vector<TimerId> scheduled;
|
|
std::vector<TimerId> cancelled;
|
|
Status schedule(TimerId id, int32_t, bool) override {
|
|
scheduled.push_back(id);
|
|
return Status::success();
|
|
}
|
|
void cancel(TimerId id) override { cancelled.push_back(id); }
|
|
};
|
|
|
|
struct Wifi : WifiProvider {
|
|
WifiStatus current;
|
|
bool savedReconnect = false;
|
|
|
|
Wifi() {
|
|
current.state = "connected";
|
|
current.ssid = "home";
|
|
current.ip = "192.168.1.5";
|
|
current.rssi = -50;
|
|
}
|
|
Status scan(std::vector<WifiNetwork>& networks) override {
|
|
const WifiNetwork network = {"home", -50, true};
|
|
networks.push_back(network);
|
|
return Status::success();
|
|
}
|
|
Status connect(const std::string* ssid, const std::string*) override {
|
|
savedReconnect = ssid == nullptr;
|
|
return Status::success();
|
|
}
|
|
WifiStatus status() const override { return current; }
|
|
void disconnect() override { current.state = "disconnected"; }
|
|
Status forget() override { return Status::success(); }
|
|
};
|
|
|
|
struct Ble : BleProvider {
|
|
int32_t duration = 0;
|
|
std::string value;
|
|
bool initialized = false;
|
|
|
|
Status init(const std::string*) override {
|
|
initialized = true;
|
|
return Status::success();
|
|
}
|
|
void deinit() override { initialized = false; }
|
|
bool isInitialized() const override { return initialized; }
|
|
Status scan(int32_t durationMs, std::vector<BleDevice>& devices) override {
|
|
duration = durationMs;
|
|
const BleDevice device = {"tag", "aa:bb", -60};
|
|
devices.push_back(device);
|
|
return Status::success();
|
|
}
|
|
Status connect(const std::string&) override { return Status::success(); }
|
|
void disconnect() override {}
|
|
bool isConnected() const override { return true; }
|
|
Status read(const std::string&, const std::string&,
|
|
std::string& out) override {
|
|
out = std::string("a\0b", 3);
|
|
return Status::success();
|
|
}
|
|
Status write(const std::string&, const std::string&,
|
|
const std::string& next) override {
|
|
value = next;
|
|
return Status::success();
|
|
}
|
|
Status startAdvertising(const std::string*) override {
|
|
return Status::success();
|
|
}
|
|
void stopAdvertising() override {}
|
|
};
|
|
|
|
struct Touch : TouchProvider {
|
|
bool down = true;
|
|
int32_t calibration[4] = {0, 0, 0, 0};
|
|
|
|
bool touch(int32_t& x, int32_t& y) const override {
|
|
x = 10;
|
|
y = 20;
|
|
return down;
|
|
}
|
|
bool rawTouch(int32_t& x, int32_t& y) const override {
|
|
x = 300;
|
|
y = 700;
|
|
return down;
|
|
}
|
|
bool isTouched() const override { return down; }
|
|
Status setCalibration(int32_t x0, int32_t y0, int32_t x1,
|
|
int32_t y1) override {
|
|
calibration[0] = x0;
|
|
calibration[1] = y0;
|
|
calibration[2] = x1;
|
|
calibration[3] = y1;
|
|
return Status::success();
|
|
}
|
|
};
|
|
|
|
struct Buttons : ButtonsProvider {
|
|
std::vector<std::string> buttons() const override {
|
|
std::vector<std::string> roles;
|
|
roles.push_back("left");
|
|
roles.push_back("right");
|
|
roles.push_back("confirm");
|
|
roles.push_back("back");
|
|
return roles;
|
|
}
|
|
bool isAnyPressed() const override { return false; }
|
|
bool isPressed(const std::string& button) const override {
|
|
return button == "confirm";
|
|
}
|
|
bool wasPressed(const std::string&) const override { return false; }
|
|
bool wasReleased(const std::string&) const override { return false; }
|
|
};
|
|
|
|
// Every provider a Runtime needs, so a test names only what it asserts on.
|
|
struct Bench {
|
|
Log log;
|
|
Settings settings;
|
|
Sys sys;
|
|
Fs fs;
|
|
Gui gui;
|
|
Http http;
|
|
Timer timer;
|
|
Wifi wifi;
|
|
Ble ble;
|
|
Touch touch;
|
|
Buttons buttons;
|
|
|
|
Providers providers() {
|
|
Providers providers;
|
|
providers.log = &log;
|
|
providers.settings = &settings;
|
|
providers.sys = &sys;
|
|
providers.fs = &fs;
|
|
providers.gui = &gui;
|
|
providers.http = &http;
|
|
providers.timer = &timer;
|
|
providers.wifi = &wifi;
|
|
providers.ble = &ble;
|
|
providers.touch = &touch;
|
|
providers.buttons = &buttons;
|
|
return providers;
|
|
}
|
|
};
|
|
|
|
} // namespace fake
|