|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +The Builder on a calculation with its MOs panel open, in every panel layout |
| 4 | +(View > Panel layout), on a laptop-sized screen. |
| 5 | +
|
| 6 | +For each layout the Builder opens the water fixture with ECCE_PANEL_MODE, |
| 7 | +ECCE_OPEN_PANEL=MOs and ECCE_PANEL_METRICS, and the metrics must show the |
| 8 | +viewer and the MOs pane on screen, inside the window, at a usable size, and |
| 9 | +every pane still a child of the frame. "default" sets no ECCE_PANEL_MODE, |
| 10 | +so it is the layout a new install gets (list + detail). |
| 11 | +
|
| 12 | +The case this was written for: fitting the window to the screen (#189) |
| 13 | +moved the AUI frame's panes into a scrolled window, after which the viewer |
| 14 | +was laid out ~6800 px tall with the molecule off screen and the MOs panel |
| 15 | +below the window's edge. |
| 16 | +
|
| 17 | +Same installed tree, isolation and services as run_tests.py (ECCE_TEST_HOME, |
| 18 | +ECCE_TEST_WRAPPERS). Exit 77 (skip) without an install, Xvfb or fixture. |
| 19 | +""" |
| 20 | + |
| 21 | +import os |
| 22 | +import re |
| 23 | +import shutil |
| 24 | +import sys |
| 25 | +import tempfile |
| 26 | + |
| 27 | +HERE = os.path.dirname(os.path.abspath(__file__)) |
| 28 | +sys.path.insert(0, HERE) |
| 29 | + |
| 30 | +import apps # noqa: E402 |
| 31 | +import fixture # noqa: E402 |
| 32 | +import run_tests # noqa: E402 |
| 33 | +import xdisplay # noqa: E402 |
| 34 | + |
| 35 | +LAYOUTS = [("classic", "classic"), ("stacked", "stacked"), |
| 36 | + ("accordion", "accordion"), ("detail", "detail"), |
| 37 | + ("default", "detail")] |
| 38 | +SCREEN = "1366x768x24" |
| 39 | +PANEL = "MOs" |
| 40 | +MIN_VIEWER = 200 # px, each way |
| 41 | +MIN_PANEL = 80 # px high: the MOs pane's own minimum |
| 42 | + |
| 43 | +failures = [] |
| 44 | + |
| 45 | + |
| 46 | +def readMetrics(path): |
| 47 | + """{'mode': str, 'client': (w, h), 'panes': {name: (x, y, w, h, on, docked)}}""" |
| 48 | + out = {"mode": None, "client": None, "panes": {}} |
| 49 | + if not os.path.exists(path): |
| 50 | + return out |
| 51 | + with open(path) as handle: |
| 52 | + for line in handle: |
| 53 | + m = re.match(r'pane "([^"]+)" (-?\d+) (-?\d+) (\d+) (\d+) (\w+) (\w+)', |
| 54 | + line) |
| 55 | + if m: |
| 56 | + out["panes"][m.group(1)] = ( |
| 57 | + int(m.group(2)), int(m.group(3)), int(m.group(4)), |
| 58 | + int(m.group(5)), m.group(6) == "onscreen", |
| 59 | + m.group(7) == "docked") |
| 60 | + elif line.startswith("client "): |
| 61 | + out["client"] = tuple(int(v) for v in line.split()[1:3]) |
| 62 | + elif line.startswith("mode "): |
| 63 | + out["mode"] = line.split()[1] |
| 64 | + return out |
| 65 | + |
| 66 | + |
| 67 | +def paneProblem(metrics, name, minW, minH): |
| 68 | + pane = metrics["panes"].get(name) |
| 69 | + if pane is None: |
| 70 | + return "%s pane not shown" % name |
| 71 | + x, y, w, h, onscreen, docked = pane |
| 72 | + cw, ch = metrics["client"] |
| 73 | + if not docked: |
| 74 | + return "%s pane moved out of the frame" % name |
| 75 | + if not onscreen: |
| 76 | + return "%s pane is not on screen" % name |
| 77 | + if x < 0 or y < 0 or x + w > cw or y + h > ch: |
| 78 | + return ("%s pane at %dx%d+%d+%d is outside the %dx%d window" |
| 79 | + % (name, w, h, x, y, cw, ch)) |
| 80 | + if w < minW or h < minH: |
| 81 | + return "%s pane is only %dx%d" % (name, w, h) |
| 82 | + return None |
| 83 | + |
| 84 | + |
| 85 | +def installCalc(): |
| 86 | + """The water fixture in the logged-in user's own home: in the fixture |
| 87 | + account the Builder stops at an "ECCE Authentication" dialog.""" |
| 88 | + user = fixture.realUser() |
| 89 | + fixture.ensureRealUserAccount() |
| 90 | + target = os.path.join(fixture.stateDir(), "htdocs", "Ecce", "users", |
| 91 | + user, "panel-layouts-water") |
| 92 | + shutil.rmtree(target, ignore_errors=True) |
| 93 | + shutil.copytree(os.path.join(fixture.FIXTURES, "calc-water-vib"), target, |
| 94 | + symlinks=True) |
| 95 | + return "http://localhost:%d/Ecce/users/%s/panel-layouts-water" % ( |
| 96 | + fixture.dataserverPort(), user) |
| 97 | + |
| 98 | + |
| 99 | +def check(display, url, label, mode, expected): |
| 100 | + metricsPath = tempfile.mktemp(prefix="ecce-panels-", suffix=".txt") |
| 101 | + env = {"ECCE_OPEN_PANEL": PANEL, "ECCE_PANEL_METRICS": metricsPath} |
| 102 | + if mode != "default": |
| 103 | + env["ECCE_PANEL_MODE"] = mode |
| 104 | + authPath = fixture.authFile( |
| 105 | + os.path.join(fixture.stateHome(), ".ECCE", "auth.pipe"), |
| 106 | + user=fixture.realUser()) |
| 107 | + # The metrics are written 12 s after the calculation's panels exist. |
| 108 | + result = apps.run(display, "builder", |
| 109 | + args=("-pipe", authPath, "-context", url), |
| 110 | + windowTimeout=60, settle=40, env=env) |
| 111 | + metrics = readMetrics(metricsPath) |
| 112 | + try: |
| 113 | + os.unlink(metricsPath) |
| 114 | + except OSError: |
| 115 | + pass |
| 116 | + log = result.log or "" |
| 117 | + problem = None |
| 118 | + if result.crashed: |
| 119 | + problem = "CRASHED (%s)" % result.signalName |
| 120 | + elif not result.sawWindow: |
| 121 | + problem = "no window" |
| 122 | + elif metrics["client"] is None: |
| 123 | + problem = "no layout metrics written" |
| 124 | + elif metrics["mode"] != expected: |
| 125 | + problem = "layout %s, expected %s" % (metrics["mode"], expected) |
| 126 | + else: |
| 127 | + moved = [n for n, p in metrics["panes"].items() if not p[5]] |
| 128 | + problem = ("panes moved out of the frame: " + ", ".join(moved) |
| 129 | + if moved else |
| 130 | + paneProblem(metrics, "Viewer", MIN_VIEWER, MIN_VIEWER) or |
| 131 | + paneProblem(metrics, PANEL, 1, MIN_PANEL)) |
| 132 | + if problem is None: |
| 133 | + print("ok %s" % label) |
| 134 | + return |
| 135 | + failures.append(label) |
| 136 | + print("FAIL %s: %s\n%s" % (label, problem, run_tests._tail(log, 15))) |
| 137 | + |
| 138 | + |
| 139 | +def main(): |
| 140 | + xdisplay.SCREEN = SCREEN |
| 141 | + |
| 142 | + def checkApp(display, name, results, verbose=False): |
| 143 | + if not fixture.available(): |
| 144 | + results.notes.append("no calculation fixture checked in; skipped") |
| 145 | + return |
| 146 | + url = installCalc() |
| 147 | + restorePrefs = fixture.settleUpgradeNotices() |
| 148 | + try: |
| 149 | + for mode, expected in LAYOUTS: |
| 150 | + check(display, url, "%s layout, %s panel" % (mode, PANEL), |
| 151 | + mode, expected) |
| 152 | + finally: |
| 153 | + restorePrefs() |
| 154 | + if failures: |
| 155 | + results.fail("panels", "%d of %d layouts wrong: %s" |
| 156 | + % (len(failures), len(LAYOUTS), ", ".join(failures))) |
| 157 | + run_tests.checkApp = checkApp |
| 158 | + sys.argv = [sys.argv[0], "--app", "builder"] + sys.argv[1:] |
| 159 | + return run_tests.main() |
| 160 | + |
| 161 | + |
| 162 | +if __name__ == "__main__": |
| 163 | + sys.exit(main()) |
0 commit comments