208 lines
10 KiB
Python
208 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Exercise CPU-speed snapshots, canonical config writes and the shipped form."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
import test_webconfig_lost_reply as web_fixture
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class CpuFrequencyWebConfigTest(unittest.TestCase):
|
|
compile_run = web_fixture.LostReplyWebConfigTest.compile_run
|
|
setup_values = web_fixture.LostReplyWebConfigTest.setup_values
|
|
run_page = web_fixture.LostReplyWebConfigTest.run_page
|
|
|
|
def test_actual_json_snapshot_and_canonical_cli_batch(self):
|
|
# Reuse the real authenticated GET/POST handlers, not a second JSON
|
|
# implementation. Only their peripheral/HTTP boundaries are faked.
|
|
source = web_fixture.LostReplyWebConfigTest.fixture(self)
|
|
source = source.split("int main() {", 1)[0] + r'''
|
|
#include <helpers/esp32/CpuClockPolicy.h>
|
|
struct CpuBoard {
|
|
uint16_t actual = 160;
|
|
int applies = 0;
|
|
bool supportsCpuFrequencyMHz(uint16_t mhz) const { return mhz == 80 || mhz == 160; }
|
|
uint16_t getCpuFrequencyMHz() const { return actual; }
|
|
bool applyCpuFrequencyMHz(uint16_t selected, bool) {
|
|
++applies; actual = selected ? selected : 80; return true;
|
|
}
|
|
};
|
|
String cpuWriteBody(const char* value, const char* key = "cpu.freq") {
|
|
return String("{\"reqid\":\"0123456789abcdef\",\"set\":{\"") + key + "\":" + value + "}}";
|
|
}
|
|
int main() {
|
|
assert(mesh::esp32::managementPassword::publish("password"));
|
|
for (unsigned mask = 0; mask < 16; ++mask) {
|
|
WebConfigServer server;
|
|
server.callbacks.node.cpu_freq_mhz = 80;
|
|
server.callbacks.node.cpu_actual_mhz = 160;
|
|
server.callbacks.node.cpu_supported_mask = mask;
|
|
AsyncWebServerRequest get;
|
|
server.handleConfigGet(&get);
|
|
JsonDocument snapshot;
|
|
assert(get.status == 200 && deserializeJson(snapshot, get.output) == DeserializationError::Ok);
|
|
if (!(mask & 7)) {
|
|
assert(snapshot["cpu"].isNull());
|
|
continue;
|
|
}
|
|
assert(snapshot["cpu"]["selected_mhz"].as<unsigned>() == 80);
|
|
assert(snapshot["cpu"]["actual_mhz"].as<unsigned>() == 160);
|
|
JsonArray options = snapshot["cpu"]["supported_mhz"];
|
|
const unsigned frequencies[] = {80, 160, 240};
|
|
unsigned count = 0;
|
|
for (unsigned bit = 0; bit < 3; ++bit) {
|
|
if (mask & (1U << bit)) assert(options[count++].as<unsigned>() == frequencies[bit]);
|
|
}
|
|
assert(options.size() == count);
|
|
}
|
|
for (const char* text : {"auto", "80", "160", "240", "81", "80;reboot", ""}) {
|
|
WebConfigServer server;
|
|
const String value = String("\"") + text + "\"";
|
|
AsyncWebServerRequest save(cpuWriteBody(value.c_str()));
|
|
server.handleConfigPost(&save);
|
|
assert(save.status == 202 && server._batch_count == 1 && !server._batch_reboot);
|
|
assert(strcmp(server._batch[0].key, "cpu.freq") == 0);
|
|
assert(String(server._batch[0].cmd) == String("set cpu.freq ") + text);
|
|
CpuBoard board;
|
|
uint16_t selected = 160;
|
|
unsigned saves = 0;
|
|
char reply[160] = {};
|
|
assert(mesh::esp32::cpuclock::handleCommand(board, selected, true,
|
|
server._batch[0].cmd, reply, sizeof(reply), [&]() { ++saves; return true; }));
|
|
const bool valid = strcmp(text, "auto") == 0 || strcmp(text, "80") == 0 || strcmp(text, "160") == 0;
|
|
if (valid) {
|
|
assert(strncmp(reply, "OK - saved", 10) == 0 && saves == 1 && board.applies == 1);
|
|
assert(selected == (strcmp(text, "auto") == 0 ? 0 : atoi(text)));
|
|
} else {
|
|
assert(strncmp(reply, "Error:", 6) == 0 && !saves && !board.applies);
|
|
assert(selected == 160 && board.actual == 160);
|
|
}
|
|
}
|
|
for (const char* invalid : {"null", "true", "80"}) {
|
|
WebConfigServer server;
|
|
AsyncWebServerRequest save(cpuWriteBody(invalid));
|
|
server.handleConfigPost(&save);
|
|
assert(save.status == 400 && server._batch_count == 0);
|
|
}
|
|
WebConfigServer server;
|
|
AsyncWebServerRequest extra(cpuWriteBody("\"80\"", "cpu.freq.extra"));
|
|
server.handleConfigPost(&extra);
|
|
assert(extra.status == 400 && server._batch_count == 0);
|
|
server.clearSessionLocked();
|
|
AsyncWebServerRequest unauthenticated(cpuWriteBody("\"80\""));
|
|
server.handleConfigPost(&unauthenticated);
|
|
assert(unauthenticated.status == 401 && server._batch_count == 0);
|
|
assert(wcIsAllowedSetKey("cpu.freq") && !wcSetKeyRequiresReboot("cpu.freq"));
|
|
}
|
|
'''
|
|
self.compile_run(source)
|
|
|
|
def test_shipped_form_uses_supported_options_and_saves_through_normal_batch(self):
|
|
status, config = self.setup_values()
|
|
status.update(mode="lan", capabilities=8192)
|
|
config["cpu"] = {"selected_mhz": 0, "actual_mhz": 80, "supported_mhz": [80, 160]}
|
|
prelude = r'''<script>
|
|
(function(){
|
|
var status=%s,config=%s,submitted=null,saves=[];
|
|
function reply(value,code){return Promise.resolve({ok:true,status:code||200,json:function(){return Promise.resolve(value)}})}
|
|
window.fetch=function(path,options){
|
|
if(path==="/api/status")return reply(status);
|
|
if(path==="/api/config"&&(!options||options.method!=="POST"))return reply(config);
|
|
if(path==="/api/config"){
|
|
submitted=JSON.parse(options.body);saves.push(submitted);
|
|
if(saves.length!==3){
|
|
config.cpu.selected_mhz=submitted.set["cpu.freq"]==="auto"?0:Number(submitted.set["cpu.freq"]);
|
|
config.cpu.actual_mhz=config.cpu.selected_mhz||80;
|
|
}
|
|
return reply({state:"pending",reqid:submitted.reqid,count:1},202);
|
|
}
|
|
if(path.indexOf("/api/config/result?")===0)return reply({state:"done",reqid:submitted.reqid,results:[{key:"cpu.freq",reply:saves.length===3?"Error: CPU clock apply failed; unchanged":"OK - saved"}]});
|
|
return reply({});
|
|
};
|
|
function mark(key,value){document.body.setAttribute("data-test-cpu-"+key,String(value))}
|
|
function waitFor(check,next,attempt){
|
|
if(check()){next();return}
|
|
if((attempt||0)>100){document.body.setAttribute("data-test-error","CPU save did not complete");return}
|
|
setTimeout(function(){waitFor(check,next,(attempt||0)+1)},20);
|
|
}
|
|
window.addEventListener("load",function(){
|
|
setTimeout(function(){enterApp()},50);
|
|
setTimeout(function(){waitFor(function(){return st.cfg&&st.cfg.cpu},function(){
|
|
var select=document.querySelector('[data-k="cpu.freq"]');
|
|
mark("visible",!document.getElementById("cpu-frequency-setting").classList.contains("hide"));
|
|
mark("enabled",!select.disabled);mark("initial",select.value);
|
|
mark("options",Array.from(select.options).map(function(o){return o.value}).join(","));
|
|
mark("actual",document.getElementById("cpu-frequency-status").textContent);
|
|
mark("clean",Object.keys(st.dirty).length===0);
|
|
select.value="160";select.dispatchEvent(new Event("input",{bubbles:true}));
|
|
mark("dirty",st.dirty["cpu.freq"]);appSave();
|
|
waitFor(function(){return saves.length===1&&st.orig["cpu.freq"]==="160"&&!st.dirty["cpu.freq"]},function(){
|
|
mark("saved",saves[0].set["cpu.freq"]);mark("only-key",Object.keys(saves[0].set).join(","));
|
|
mark("no-reboot",!saves[0].reboot);
|
|
mark("after-actual",document.getElementById("cpu-frequency-status").textContent);
|
|
select.value="auto";select.dispatchEvent(new Event("input",{bubbles:true}));appSave();
|
|
waitFor(function(){return saves.length===2&&st.orig["cpu.freq"]==="auto"&&!st.dirty["cpu.freq"]},function(){
|
|
mark("auto-saved",saves[1].set["cpu.freq"]);
|
|
select.value="160";select.dispatchEvent(new Event("input",{bubbles:true}));appSave();
|
|
waitFor(function(){return saves.length===3&&!document.getElementById("save-btn").disabled&&select.closest(".f").querySelector(".chip.err")},function(){
|
|
mark("rejected-dirty",st.dirty["cpu.freq"]);mark("rejected-saved",st.orig["cpu.freq"]);
|
|
mark("rejected-field",select.value);mark("rejected-actual",document.getElementById("cpu-frequency-status").textContent);
|
|
setTimeout(function(){mark("requests",saves.length)},200);
|
|
});
|
|
});
|
|
});
|
|
});},200);
|
|
});
|
|
})();
|
|
</script>''' % (json.dumps(status), json.dumps(config))
|
|
dom = self.run_page(prelude, virtual_time=4000)
|
|
for key, value in (("visible", "true"), ("enabled", "true"), ("initial", "auto"),
|
|
("options", "auto,80,160"), ("actual", "Running at 80 MHz"),
|
|
("clean", "true"), ("dirty", "160"), ("saved", "160"),
|
|
("only-key", "cpu.freq"), ("no-reboot", "true"),
|
|
("after-actual", "Running at 160 MHz"), ("auto-saved", "auto"),
|
|
("rejected-dirty", "160"), ("rejected-saved", "auto"),
|
|
("rejected-field", "160"), ("rejected-actual", "Running at 80 MHz"),
|
|
("requests", "3")):
|
|
self.assertIn('data-test-cpu-%s="%s"' % (key, value), dom)
|
|
|
|
def test_shipped_form_hides_unavailable_clock_and_can_load_240(self):
|
|
for cpu, values, visible, selected in (
|
|
(None, "auto", False, "auto"),
|
|
({"selected_mhz": 240, "actual_mhz": 240,
|
|
"supported_mhz": [240, 80, 160, 240, 20]}, "auto,80,160,240", True, "240")):
|
|
with self.subTest(cpu=cpu):
|
|
status, config = self.setup_values()
|
|
status.update(mode="lan")
|
|
if cpu is not None:
|
|
config["cpu"] = cpu
|
|
else:
|
|
config.pop("cpu", None)
|
|
prelude = r'''<script>
|
|
window.fetch=function(path){var value=path==="/api/status"?%s:%s;return Promise.resolve({ok:true,status:200,json:function(){return Promise.resolve(value)}})};
|
|
window.addEventListener("load",function(){
|
|
setTimeout(function(){enterApp()},50);
|
|
setTimeout(function(){
|
|
var select=document.querySelector('[data-k="cpu.freq"]'),body=document.body;
|
|
body.setAttribute("data-test-cpu-visible",!document.getElementById("cpu-frequency-setting").classList.contains("hide"));
|
|
body.setAttribute("data-test-cpu-disabled",select.disabled);
|
|
body.setAttribute("data-test-cpu-options",Array.from(select.options).map(function(o){return o.value}).join(","));
|
|
body.setAttribute("data-test-cpu-selected",select.value);
|
|
body.setAttribute("data-test-cpu-dirty",Object.keys(st.dirty).length);
|
|
},650);
|
|
});
|
|
</script>''' % (json.dumps(status), json.dumps(config))
|
|
dom = self.run_page(prelude)
|
|
for key, value in (("visible", str(visible).lower()),
|
|
("disabled", str(not visible).lower()),
|
|
("options", values), ("selected", selected), ("dirty", "0")):
|
|
self.assertIn('data-test-cpu-%s="%s"' % (key, value), dom)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|