Skip to content

Commit db80a5a

Browse files
committed
wsgibinsupp: implement support code for the testing of wsgibin related code
1 parent a186fa5 commit db80a5a

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

tests/support/wsgibinsupp.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# --- BEGIN_HEADER ---
4+
#
5+
# htmlsupp - test support library for WSGI
6+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
7+
#
8+
# This file is part of MiG.
9+
#
10+
# MiG is free software: you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; either version 2 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# MiG is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23+
#
24+
# -- END_HEADER ---
25+
#
26+
27+
from collections import namedtuple
28+
import codecs
29+
from io import BytesIO
30+
31+
from mig.shared.output import format_output
32+
import mig.shared.returnvalues as returnvalues
33+
34+
35+
def _is_return_value(return_value):
36+
defined_return_values = returnvalues.__dict__.values()
37+
return return_value in defined_return_values
38+
39+
40+
def create_instrumented_format_output():
41+
def _instrumented_format_output(
42+
configuration,
43+
backend,
44+
ret_val,
45+
ret_msg,
46+
out_obj,
47+
outputformat,
48+
):
49+
# record the call args
50+
# capture the original before altering it
51+
call_args_out_obj = list(out_obj)
52+
call_args = (configuration, backend, ret_val, ret_msg,
53+
call_args_out_obj, outputformat,)
54+
_instrumented_format_output.calls.append({'args': call_args})
55+
56+
# FIXME: the following is a workaround for a bug that exists between the WSGI wrapper
57+
# and the output formatter - specifically, the latter adds default header and
58+
# title if start does not exist, but the former ensures that start always exists
59+
# meaning that a default response under WSGI is missing half the HTML.
60+
start_obj_idx = next((i for i, obj in enumerate(
61+
out_obj) if obj['object_type'] == 'start'))
62+
insertion_idx = start_obj_idx
63+
64+
# FIXME: format_output() is sensitive to ordering and MUST see a title object _before_
65+
# anything else otherwise the preamble ends up written above the header and thus
66+
# an invalid HTML page is served.
67+
insertion_idx += 1
68+
out_obj.insert(insertion_idx, {
69+
'object_type': 'title',
70+
'text': _instrumented_format_output.values['title_text'],
71+
'meta': '',
72+
'style': {},
73+
'script': {},
74+
})
75+
76+
insertion_idx += 1
77+
out_obj.insert(insertion_idx, {
78+
'object_type': 'header',
79+
'text': _instrumented_format_output.values['header_text']
80+
})
81+
82+
return format_output(
83+
configuration,
84+
backend,
85+
ret_val,
86+
ret_msg,
87+
out_obj,
88+
outputformat,
89+
)
90+
_instrumented_format_output.calls = []
91+
_instrumented_format_output.values = dict(
92+
title_text='',
93+
header_text='',
94+
)
95+
96+
def _program_values(**kwargs):
97+
_instrumented_format_output.values.update(kwargs)
98+
99+
_instrumented_format_output.set_values = _program_values
100+
101+
return _instrumented_format_output
102+
103+
104+
def create_instrumented_retrieve_handler():
105+
def _simulated_action(*args):
106+
return _simulated_action.returning or ([], returnvalues.ERROR)
107+
_simulated_action.calls = []
108+
_simulated_action.returning = None
109+
110+
def _program_response(output_objects=None, return_value=None):
111+
assert _is_return_value(
112+
return_value), "return value must be present in returnvalues"
113+
assert isinstance(output_objects, list)
114+
_simulated_action.returning = (output_objects, return_value)
115+
116+
def _instrumented_retrieve_handler(*args):
117+
_instrumented_retrieve_handler.calls.append(tuple(args))
118+
return _simulated_action
119+
_instrumented_retrieve_handler.calls = []
120+
121+
_instrumented_retrieve_handler.program = _program_response
122+
_instrumented_retrieve_handler.simulated = _simulated_action
123+
124+
return _instrumented_retrieve_handler
125+
126+
127+
class WsgibinInstrumentation:
128+
def __init__(self):
129+
self.format_output = create_instrumented_format_output()
130+
self.retrieve_handler = create_instrumented_retrieve_handler()
131+
132+
def set_response(self, content, returnvalue):
133+
self.retrieve_handler.program(content, returnvalue)
134+
135+
136+
class WsgibinAssertMixin:
137+
def assertWsgibinInstrumentation(self, instrumentation=None):
138+
if instrumentation is None:
139+
instrumentation = getattr(self, 'wsgibin_instrumentation', None)
140+
assert isinstance(instrumentation, WsgibinInstrumentation)
141+
142+
simulated_action = instrumentation.retrieve_handler.simulated
143+
self.assertIsNotNone(simulated_action.returning,
144+
"no response programmed")
145+
146+
def was_called(fake):
147+
assert hasattr(fake, 'calls')
148+
return len(fake.calls) > 0
149+
150+
self.assertTrue(was_called(
151+
instrumentation.format_output), "no output generated")
152+
self.assertTrue(was_called(
153+
instrumentation.retrieve_handler), "no output generated")

0 commit comments

Comments
 (0)