CustomScreenTestImpl.groovy
15.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/*
* This software is in the public domain under CC0 1.0 Universal plus a
* Grant of Patent License.
*
* To the extent possible under law, author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software (see the LICENSE.md file). If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
package org.moqui.mcp
import groovy.transform.CompileStatic
import org.apache.shiro.subject.Subject
import org.moqui.BaseArtifactException
import org.moqui.util.ContextStack
import org.moqui.impl.context.ExecutionContextFactoryImpl
import org.moqui.impl.context.ExecutionContextImpl
import org.moqui.impl.screen.ScreenDefinition
import org.moqui.impl.screen.ScreenFacadeImpl
import org.moqui.impl.screen.ScreenTestImpl
import org.moqui.impl.screen.ScreenUrlInfo
import org.moqui.screen.ScreenRender
import org.moqui.screen.ScreenTest
import org.moqui.util.MNode
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.concurrent.Future
@CompileStatic
class CustomScreenTestImpl implements ScreenTest {
protected final static Logger logger = LoggerFactory.getLogger(CustomScreenTestImpl.class)
protected final ExecutionContextFactoryImpl ecfi
protected final ScreenFacadeImpl sfi
// see FtlTemplateRenderer.MoquiTemplateExceptionHandler, others
final List<String> errorStrings = ["[Template Error", "FTL stack trace", "Could not find subscreen or transition"]
protected String rootScreenLocation = null
protected ScreenDefinition rootScreenDef = null
protected String baseScreenPath = null
protected List<String> baseScreenPathList = null
protected ScreenDefinition baseScreenDef = null
protected String outputType = null
protected String characterEncoding = null
protected String macroTemplateLocation = null
protected String baseLinkUrl = null
protected String servletContextPath = null
protected String webappName = null
protected boolean skipJsonSerialize = false
protected static final String hostname = "localhost"
long renderCount = 0, errorCount = 0, totalChars = 0, startTime = System.currentTimeMillis()
final Map<String, Object> sessionAttributes = [:]
CustomScreenTestImpl(ExecutionContextFactoryImpl ecfi) {
this.ecfi = ecfi
sfi = ecfi.screenFacade
// init default webapp, root screen
webappName('webroot')
}
@Override
ScreenTest rootScreen(String screenLocation) {
rootScreenLocation = screenLocation
rootScreenDef = sfi.getScreenDefinition(rootScreenLocation)
if (rootScreenDef == null) throw new IllegalArgumentException("Root screen not found: ${rootScreenLocation}")
baseScreenDef = rootScreenDef
return this
}
@Override
ScreenTest baseScreenPath(String screenPath) {
if (!rootScreenLocation) throw new BaseArtifactException("No rootScreen specified")
baseScreenPath = screenPath
if (baseScreenPath.endsWith("/")) baseScreenPath = baseScreenPath.substring(0, baseScreenPath.length() - 1)
if (baseScreenPath) {
baseScreenPathList = ScreenUrlInfo.parseSubScreenPath(rootScreenDef, rootScreenDef, [], baseScreenPath, null, sfi)
if (baseScreenPathList == null) throw new BaseArtifactException("Error in baseScreenPath, could find not base screen path ${baseScreenPath} under ${rootScreenDef.location}")
for (String screenName in baseScreenPathList) {
ScreenDefinition.SubscreensItem ssi = baseScreenDef.getSubscreensItem(screenName)
if (ssi == null) throw new BaseArtifactException("Error in baseScreenPath, could not find ${screenName} under ${baseScreenDef.location}")
baseScreenDef = sfi.getScreenDefinition(ssi.location)
if (baseScreenDef == null) throw new BaseArtifactException("Error in baseScreenPath, could not find screen ${screenName} at ${ssi.location}")
}
}
return this
}
@Override ScreenTest renderMode(String outputType) { this.outputType = outputType; return this }
@Override ScreenTest encoding(String characterEncoding) { this.characterEncoding = characterEncoding; return this }
@Override ScreenTest macroTemplate(String macroTemplateLocation) { this.macroTemplateLocation = macroTemplateLocation; return this }
@Override ScreenTest baseLinkUrl(String baseLinkUrl) { this.baseLinkUrl = baseLinkUrl; return this }
@Override ScreenTest servletContextPath(String scp) { this.servletContextPath = scp; return this }
@Override ScreenTest skipJsonSerialize(boolean skip) { this.skipJsonSerialize = skip; return this }
@Override
ScreenTest webappName(String wan) {
webappName = wan
// set a default root screen based on config for "localhost"
MNode webappNode = ecfi.getWebappNode(webappName)
for (MNode rootScreenNode in webappNode.children("root-screen")) {
if (hostname.matches(rootScreenNode.attribute('host'))) {
String rsLoc = rootScreenNode.attribute('location')
rootScreen(rsLoc)
break
}
}
return this
}
@Override
List<String> getNoRequiredParameterPaths(Set<String> screensToSkip) {
if (!rootScreenLocation) throw new IllegalStateException("No rootScreen specified")
List<String> noReqParmLocations = baseScreenDef.nestedNoReqParmLocations("", screensToSkip)
// logger.info("======= rootScreenLocation=${rootScreenLocation}\nbaseScreenPath=${baseScreenPath}\nbaseScreenDef: ${baseScreenDef.location}\nnoReqParmLocations: ${noReqParmLocations}")
return noReqParmLocations
}
@Override
ScreenTestRender render(String screenPath, Map<String, Object> parameters, String requestMethod) {
if (!rootScreenLocation) throw new IllegalArgumentException("No rootScreenLocation specified")
return new CustomScreenTestRenderImpl(this, screenPath, parameters, requestMethod).render()
}
@Override
void renderAll(List<String> screenPathList, Map<String, Object> parameters, String requestMethod) {
// NOTE: using single thread for now, doesn't actually make a lot of difference in overall test run time
int threads = 1
if (threads == 1) {
for (String screenPath in screenPathList) {
ScreenTestRender str = render(screenPath, parameters, requestMethod)
logger.info("Rendered ${screenPath} in ${str.getRenderTime()}ms, ${str.output?.length()} characters")
}
} else {
ExecutionContextImpl eci = ecfi.getEci()
ArrayList<Future> threadList = new ArrayList<Future>(threads)
int screenPathListSize = screenPathList.size()
for (int si = 0; si < screenPathListSize; si++) {
String screenPath = (String) screenPathList.get(si)
threadList.add(eci.runAsync({
ScreenTestRender str = render(screenPath, parameters, requestMethod)
logger.info("Rendered ${screenPath} in ${str.getRenderTime()}ms, ${str.output?.length()} characters")
}))
if (threadList.size() == threads || (si + 1) == screenPathListSize) {
for (int i = 0; i < threadList.size(); i++) { ((Future) threadList.get(i)).get() }
threadList.clear()
}
}
}
}
long getRenderCount() { return renderCount }
long getErrorCount() { return errorCount }
long getRenderTotalChars() { return totalChars }
long getStartTime() { return startTime }
@CompileStatic
static class CustomScreenTestRenderImpl implements ScreenTestRender {
protected final CustomScreenTestImpl sti
String screenPath = (String) null
Map<String, Object> parameters = [:]
String requestMethod = (String) null
ScreenRender screenRender = (ScreenRender) null
String outputString = (String) null
Object jsonObj = null
long renderTime = 0
Map postRenderContext = (Map) null
protected List<String> errorMessages = []
CustomScreenTestRenderImpl(CustomScreenTestImpl sti, String screenPath, Map<String, Object> parameters, String requestMethod) {
this.sti = sti
this.screenPath = screenPath
if (parameters != null) this.parameters.putAll(parameters)
this.requestMethod = requestMethod
}
ScreenTestRender render() {
// render in separate thread with an independent ExecutionContext so it doesn't muck up the current one
ExecutionContextFactoryImpl ecfi = sti.ecfi
ExecutionContextImpl localEci = ecfi.getEci()
String username = localEci.userFacade.getUsername()
Subject loginSubject = localEci.userFacade.getCurrentSubject()
boolean authzDisabled = localEci.artifactExecutionFacade.getAuthzDisabled()
CustomScreenTestRenderImpl stri = this
Throwable threadThrown = null
Thread newThread = new Thread("CustomScreenTestRender") {
@Override void run() {
try {
ExecutionContextImpl threadEci = ecfi.getEci()
if (loginSubject != null) threadEci.userFacade.internalLoginSubject(loginSubject)
else if (username != null && !username.isEmpty()) threadEci.userFacade.internalLoginUser(username)
if (authzDisabled) threadEci.artifactExecutionFacade.disableAuthz()
// as this is used for server-side transition calls don't do tarpit checks
threadEci.artifactExecutionFacade.disableTarpit()
renderInternal(threadEci, stri)
threadEci.destroy()
} catch (Throwable t) {
threadThrown = t
}
}
}
newThread.start()
newThread.join()
if (threadThrown != null) throw threadThrown
return this
}
private static void renderInternal(ExecutionContextImpl eci, CustomScreenTestRenderImpl stri) {
CustomScreenTestImpl sti = stri.sti
long startTime = System.currentTimeMillis()
// parse the screenPath - if empty or null, use empty list to render the root screen
ArrayList<String> screenPathList = []
if (stri.screenPath != null && !stri.screenPath.trim().isEmpty()) {
screenPathList = ScreenUrlInfo.parseSubScreenPath(sti.rootScreenDef, sti.baseScreenDef,
sti.baseScreenPathList, stri.screenPath, stri.parameters, sti.sfi)
if (screenPathList == null) throw new BaseArtifactException("Could not find screen path ${stri.screenPath} under base screen ${sti.baseScreenDef.location}")
}
// push context
ContextStack cs = eci.getContext()
cs.push()
// create our custom WebFacadeStub instead of framework's, passing screen path for proper path handling
WebFacadeStub wfs = new WebFacadeStub(sti.ecfi, stri.parameters, sti.sessionAttributes, stri.requestMethod, stri.screenPath)
// set stub on eci, will also put parameters in context
eci.setWebFacade(wfs)
// make the ScreenRender
ScreenRender screenRender = sti.sfi.makeRender()
stri.screenRender = screenRender
// pass through various settings
if (sti.rootScreenLocation != null && sti.rootScreenLocation.length() > 0) screenRender.rootScreen(sti.rootScreenLocation)
if (sti.outputType != null && sti.outputType.length() > 0) screenRender.renderMode(sti.outputType)
if (sti.characterEncoding != null && sti.characterEncoding.length() > 0) screenRender.encoding(sti.characterEncoding)
if (sti.macroTemplateLocation != null && sti.macroTemplateLocation.length() > 0) screenRender.macroTemplate(sti.macroTemplateLocation)
if (sti.baseLinkUrl != null && sti.baseLinkUrl.length() > 0) screenRender.baseLinkUrl(sti.baseLinkUrl)
if (sti.servletContextPath != null && sti.servletContextPath.length() > 0) screenRender.servletContextPath(sti.servletContextPath)
screenRender.webappName(sti.webappName)
if (sti.skipJsonSerialize) wfs.skipJsonSerialize = true
// set the screenPath
screenRender.screenPath(screenPathList)
// do the render
try {
screenRender.render(wfs.httpServletRequest, wfs.httpServletResponse)
// get the response text from our WebFacadeStub
stri.outputString = wfs.getResponseText()
stri.jsonObj = wfs.getResponseJsonObj()
System.out.println("STRI: " + stri.outputString + " : " + stri.jsonObj)
} catch (Throwable t) {
String errMsg = "Exception in render of ${stri.screenPath}: ${t.toString()}"
logger.warn(errMsg, t)
stri.errorMessages.add(errMsg)
sti.errorCount++
}
// calc renderTime
stri.renderTime = System.currentTimeMillis() - startTime
// pop the context stack, get rid of var space
stri.postRenderContext = cs.pop()
// check, pass through, error messages
if (eci.message.hasError()) {
stri.errorMessages.addAll(eci.message.getErrors())
eci.message.clearErrors()
StringBuilder sb = new StringBuilder("Error messages from ${stri.screenPath}: ")
for (String errorMessage in stri.errorMessages) sb.append("\n").append(errorMessage)
logger.warn(sb.toString())
sti.errorCount += stri.errorMessages.size()
}
// check for error strings in output
if (stri.outputString != null) for (String errorStr in sti.errorStrings) if (stri.outputString.contains(errorStr)) {
String errMsg = "Found error [${errorStr}] in output from ${stri.screenPath}"
stri.errorMessages.add(errMsg)
sti.errorCount++
logger.warn(errMsg)
}
// update stats
sti.renderCount++
if (stri.outputString != null) sti.totalChars += stri.outputString.length()
}
@Override ScreenRender getScreenRender() { return screenRender }
@Override String getOutput() { return outputString }
@Override Object getJsonObject() { return jsonObj }
@Override long getRenderTime() { return renderTime }
@Override Map getPostRenderContext() { return postRenderContext }
@Override List<String> getErrorMessages() { return errorMessages }
@Override
boolean assertContains(String text) {
if (!outputString) return false
return outputString.contains(text)
}
@Override
boolean assertNotContains(String text) {
if (!outputString) return true
return !outputString.contains(text)
}
@Override
boolean assertRegex(String regex) {
if (!outputString) return false
return outputString.matches(regex)
}
}
}