Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
## Stafl Fork Notes

- Consuming projects, generally only `stafl-test-framework-common`, should pin to specific commit hashes; tags will not be generated.
- The web GUI's prebuilt `openhtf/output/web_gui/dist/` must be rebuilt and committed after changing anything under `web_gui/src/`. Its Angular 4 / webpack 3 toolchain builds with Node 16 (newer Node fails; Node 10 can't get a node-sass binary); unchanged sources reproduce the committed `dist/` byte for byte. From `openhtf/output/web_gui/`:

```bash
docker run --rm --platform linux/amd64 -v "$PWD":/app -w /app node:16 \
sh -c "npm ci --ignore-scripts && npm rebuild node-sass && npm run build"
```

`--ignore-scripts` skips the e2e webdriver download, so `npm rebuild node-sass` fetches node-sass's binary. Delete the `node_modules/` it leaves behind afterwards.
- Protoc must be installed on the path to use this package. Reference the `Install protoc` actions for each OS in <https://github.com/StaflSystems/stafl-test-framework-common/blob/main/.github/workflows/python-package.yml> for up-to-date installation directions.

# OpenHTF
Expand Down
19 changes: 19 additions & 0 deletions openhtf/core/test_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,25 @@ def abort_from_sig_int(self) -> None:
_LOG.error('Test state: %s', self._executor.test_state)
self._executor.abort()

def abort(self) -> bool:
"""Abort the running test, as when an operator stops it.

The test ends with outcome ABORTED, and its teardown phases and output
callbacks still run. Unlike a second SIGINT, calling this again never
forcibly stops the process: once an abort is under way, further calls do
nothing, so a double-clicked stop button can't skip teardown.

Returns:
True if this call started the abort; False if no test was running or an
abort was already under way.
"""
with self._lock:
if not self._executor or self._executor.abort_requested:
return False
_LOG.error('Aborting %s: stop requested', self)
self._executor.abort()
return True

def execute(self,
test_start: Optional[Union[phase_descriptor.PhaseT,
Callable[[], str]]] = None,
Expand Down
5 changes: 5 additions & 0 deletions openhtf/core/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ def close(self) -> None:
self.wait()
self.running_test_state.close()

@property
def abort_requested(self) -> bool:
"""Whether abort() has been called on this executor."""
return self._abort.is_set()

def abort(self) -> None:
"""Abort this test."""
if self._abort.is_set():
Expand Down
4 changes: 4 additions & 0 deletions openhtf/core/test_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,10 @@ def _asdict(self) -> Dict[Text, Any]:
def is_finalized(self) -> bool:
return self._status == self.Status.COMPLETED

@property
def status(self) -> 'TestState.Status':
return self._status

def stop_running_phase(self) -> None:
"""Stops the currently running phase, allowing another phase to run."""
self.running_phase_state = None
Expand Down
57 changes: 57 additions & 0 deletions openhtf/output/servers/station_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import time
import types
from typing import Optional, Union
import urllib.parse

import openhtf
from openhtf.output.callbacks import mfg_inspector
Expand All @@ -44,6 +45,7 @@
from openhtf.util import multicast
from openhtf.util import timeouts
import sockjs.tornado
import tornado.web

CONF = configuration.CONF

Expand Down Expand Up @@ -387,6 +389,60 @@ def get(self, test_uid):
self.write({'data': phase_descriptors})


def _is_same_origin_json(request) -> bool:
"""Whether a request is a JSON POST from a page served by this server.

A browser can send a cross-origin POST with a form-like body without asking
first, so requiring JSON makes it ask (CORS preflight), which this server
never grants for the abort endpoint. A request with an Origin header must
also come from this host and port. Requests without one (not from a
browser) are let through, like every other endpoint here.
"""
content_type = request.headers.get('Content-Type', '')
if content_type.split(';')[0].strip().lower() != 'application/json':
return False
origin = request.headers.get('Origin')
if origin is None:
return True
return urllib.parse.urlsplit(origin).netloc == request.host


class AbortHandler(tornado.web.RequestHandler):
"""POST endpoint the station's own web UI calls to stop the running test.

The test ends ABORTED and its teardown phases and output callbacks still run
(see Test.abort). Unlike the other endpoints this one sends no CORS headers
and only accepts a JSON request from a page this server served, so another
web page open on a machine that can reach the station can't stop a test.
That also means the dashboard, which is served from a different port, can't
stop a test.
"""

def post(self, test_uid):
if not _is_same_origin_json(self.request):
self.set_status(403)
self.write('Stop requests must come from this station\'s web UI.')
return

test, test_state = _get_executing_test()
if test is None or str(test.uid) != test_uid:
self.set_status(404)
self.write('Unknown test UID %s' % test_uid)
return

if test_state.status != test_state.Status.RUNNING:
self.set_status(409)
self.write('The test is not running.')
return

if not test.abort():
self.set_status(409)
self.write('The test is already stopping.')
return

self.set_status(204)


class PlugsHandler(BaseTestHandler):
"""POST endpoints to receive plug responses from the frontend."""

Expand Down Expand Up @@ -639,6 +695,7 @@ def __init__(
# Set up the other endpoints.
routes.extend((
(r'/tests/(?P<test_uid>[\w\d:]+)/phases', PhasesHandler),
(r'/tests/(?P<test_uid>[\w\d:]+)/abort', AbortHandler),
(r'/tests/(?P<test_uid>[\w\d:]+)/plugs/(?P<plug_name>.+)',
PlugsHandler),
(r'/tests/(?P<test_uid>[\w\d:]+)/phases/(?P<phase_descriptor_id>\d+)/'
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions openhtf/output/web_gui/dist/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<head><link href="/css/app.77df9acbfc40a7330df9.css" rel="stylesheet"></head><!doctype html>
<head><link href="/css/app.689435824e2b68133fe2.css" rel="stylesheet"></head><!doctype html>
<!--
Copyright 2022 Google LLC

Expand All @@ -22,4 +22,4 @@

<base href="/">
<htf-app config="{{ json_encode(config) }}">Loading...</htf-app>
<script type="text/javascript" src="/js/polyfills.77df9acbfc40a7330df9.js"></script><script type="text/javascript" src="/js/vendor.77df9acbfc40a7330df9.js"></script><script type="text/javascript" src="/js/app.77df9acbfc40a7330df9.js"></script>
<script type="text/javascript" src="/js/polyfills.689435824e2b68133fe2.js"></script><script type="text/javascript" src="/js/vendor.689435824e2b68133fe2.js"></script><script type="text/javascript" src="/js/app.689435824e2b68133fe2.js"></script>

Large diffs are not rendered by default.

Large diffs are not rendered by default.

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
&nbsp;{{ test | elapsedTime:'(%s)' }}
</div>
<div class="u-flex-grow"></div>
<button
*ngIf="canStop"
type="button"
class="htf-rounded-button-red stop-button"
[disabled]="stopRequested"
(click)="stopTest()">
{{ stopRequested ? 'Stopping…' : 'Stop test' }}
</button>
<div
class="htf-status-indicator"
[ngClass]="test.status | statusToClass">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
line-height: 53px;
}

.stop-button {
margin-right: $widget-inner-padding;
}

.progress-bar-container {
align-items: center;
display: flex;
Expand Down
Loading
Loading