mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
131 test modules sat next to the code they test; instances alone had 51 of them beside views.py and utils.py. They move into a tests/ package per app, and the JavaScript checks into webvirtcloud/tests/js. Each non-empty tests.py becomes tests/test_views.py; the empty generated stubs are removed. Imports of the app's own modules become absolute, paths built from __file__ go one level up, the live helper is instances.tests.livetest, and coverage omits */tests/*. The suite discovers the same 960 tests as before.
114 lines
4.9 KiB
Python
114 lines
4.9 KiB
Python
"""The new-VM dialog's CPU bars: the page carries each compute's statistics
|
|
URL and reads no CPU usage itself; its script fills the bars when the dialog
|
|
opens, two computes at a time, and ignores answers of an earlier opening."""
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import unittest
|
|
from unittest.mock import PropertyMock, patch
|
|
|
|
from computes.models import Compute
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
from lxml import html
|
|
|
|
RUN = """
|
|
const vm = require("vm");
|
|
function element() {
|
|
const attrs = {};
|
|
return {style: {}, textContent: "", attrs,
|
|
setAttribute: (k, v) => { attrs[k] = String(v); }, removeAttribute: (k) => { delete attrs[k]; }};
|
|
}
|
|
const cells = URLS.map((url) => {
|
|
const bar = element(), label = element();
|
|
return {dataset: {url}, bar, label,
|
|
querySelector: (sel) => (sel === ".progress-bar" ? bar : label)};
|
|
});
|
|
const handlers = {}, requests = [];
|
|
const dialog = {addEventListener: (name, fn) => { handlers[name] = fn; }, querySelectorAll: () => cells};
|
|
const page = {
|
|
document: {getElementById: () => dialog},
|
|
$: {getJSON: (url) => {
|
|
const r = {url, done: (f) => { r.ok = f; return r; }, fail: (f) => { r.err = f; return r; },
|
|
always: (f) => { r.end = f; return r; }};
|
|
requests.push(r);
|
|
return r;
|
|
}},
|
|
window: {location: {}},
|
|
};
|
|
vm.runInNewContext(SCRIPT, vm.createContext(page));
|
|
const state = () => cells.map((c) => [c.bar.style.width, c.bar.attrs["aria-valuenow"] || null, c.label.textContent]);
|
|
const answer = (i, data) => { const r = requests[i]; data === null ? r.err() : r.ok(data); r.end(); };
|
|
const out = {};
|
|
handlers["show.bs.modal"]();
|
|
out.loading = state();
|
|
out.first = requests.map((r) => r.url);
|
|
handlers["hide.bs.modal"]();
|
|
answer(0, {cpudata: 5});
|
|
answer(1, null);
|
|
out.hidden = [state(), requests.length];
|
|
handlers["show.bs.modal"]();
|
|
answer(2, {cpudata: 0});
|
|
answer(3, {cpudata: null});
|
|
out.filled = state();
|
|
handlers["hide.bs.modal"]();
|
|
handlers["show.bs.modal"]();
|
|
out.reopened = requests.length;
|
|
answer(4, {cpudata: 99});
|
|
answer(5, {cpudata: 41.6});
|
|
answer(6, null);
|
|
out.final = state();
|
|
out.all = requests.map((r) => r.url);
|
|
console.log(JSON.stringify(out));
|
|
"""
|
|
|
|
|
|
class CreateDialogCpuTestCase(TestCase):
|
|
def setUp(self):
|
|
self.client.force_login(get_user_model().objects.create_superuser("cd-admin", "cd@example.com", "pw"))
|
|
self.computes = [
|
|
Compute.objects.create(name=f"cd{i}", hostname=f"192.0.2.{i}", login="u", password="p", type=1)
|
|
for i in (1, 2, 3)
|
|
]
|
|
patches = [
|
|
patch.object(Compute, name, new_callable=PropertyMock, return_value=value)
|
|
for name, value in (("status", True), ("cpu_count", 1), ("ram_size", 1024), ("ram_usage", 0))
|
|
]
|
|
patches += [patch("instances.views.utils.refr"), patch("computes.models.wvmHostDetails")]
|
|
for p in patches:
|
|
self.details = p.start() # the last one: the host connection
|
|
self.addCleanup(p.stop)
|
|
|
|
def page(self):
|
|
response = self.client.get(reverse("instances:index"))
|
|
self.assertEqual(response.status_code, 200)
|
|
return response.content.decode()
|
|
|
|
def test_the_page_carries_the_statistics_urls_and_reads_no_cpu_usage(self):
|
|
page = self.page()
|
|
urls = html.fromstring(page).xpath("//td[@class='cpu-usage']/@data-url")
|
|
self.assertEqual(urls, [reverse("compute_graph", args=[c.id]) for c in self.computes])
|
|
self.details.return_value.get_cpu_usage.assert_not_called()
|
|
|
|
@unittest.skipUnless(shutil.which("node"), "node is not installed")
|
|
def test_the_dialog_fills_the_bars_two_at_a_time(self):
|
|
page = self.page()
|
|
script = next(s for s in html.fromstring(page).xpath("//script[not(@src)]/text()") if "show.bs.modal" in s)
|
|
urls = [reverse("compute_graph", args=[c.id]) for c in self.computes]
|
|
js = RUN.replace("SCRIPT", json.dumps(script)).replace("URLS", json.dumps(urls))
|
|
out = subprocess.run(["node", "-e", js], capture_output=True, text=True, timeout=30)
|
|
self.assertEqual(out.returncode, 0, out.stderr)
|
|
result = json.loads(out.stdout)
|
|
self.assertEqual(result["loading"], [["0%", None, "…"]] * 3)
|
|
self.assertEqual(result["first"], urls[:2])
|
|
# answers that come after the dialog closed change nothing and read no more
|
|
self.assertEqual(result["hidden"], [[["0%", None, "…"]] * 3, 2])
|
|
# 0 is a value, no value is a dash
|
|
self.assertEqual(result["filled"], [["0%", "0", "0%"], ["0%", None, "—"], ["0%", None, "…"]])
|
|
# reopened while the third read runs: one more read, two in all
|
|
self.assertEqual(result["reopened"], 6)
|
|
# the earlier opening's answer (99) is ignored; a failed read is a dash
|
|
self.assertEqual(result["final"], [["42%", "42", "42%"], ["0%", None, "—"], ["0%", None, "…"]])
|
|
self.assertEqual(result["all"], urls[:2] + urls + urls)
|