Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
act.test.TestSession Maven / Gradle / Ivy
package act.test;
/*-
* #%L
* ACT Framework
* %%
* Copyright (C) 2014 - 2019 ActFramework
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import act.Act;
import act.app.App;
import act.metric.Metric;
import act.metric.MetricInfo;
import act.metric.Timer;
import act.test.func.Func;
import act.test.util.*;
import act.test.verifier.Verifier;
import act.util.LogSupport;
import act.util.ProgressGauge;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.osgl.$;
import org.osgl.exception.UnexpectedException;
import org.osgl.util.*;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import static act.test.Test.PG_PAYLOAD_FAILED;
import static act.test.util.ErrorMessage.errorIf;
import static act.test.util.ErrorMessage.errorIfNot;
/**
* A single test session encapsulate a full cycle of test run including
*
* * clear fixture
* * run dependency scenarios
* * run target scenario
*
* For each scenario run it contains:
*
* * load fixtures
* * run scenario
*
*/
public class TestSession extends LogSupport {
private final static ThreadLocal current = new ThreadLocal<>();
static TestSession current() {
return current.get();
}
private boolean proceed;
private Scenario running;
private Scenario target;
private List dependencies = new ArrayList<>();
private App app;
private int port = 5460;
private OkHttpClient http;
private CookieStore cookieStore;
private transient Metric metric = Act.metricPlugin().metric(MetricInfo.ACT_TEST_SCENARIO);
$.Var lastData = $.var();
$.Var lastHeaders = $.var();
public Map constants = new HashMap<>();
Map cache = new HashMap<>();
public RequestTemplateManager requestTemplateManager;
public TestSession(Scenario scenario, RequestTemplateManager requestTemplateManager) {
this.requestTemplateManager = requestTemplateManager;
dependencies.addAll(scenario.allDepends);
target = scenario;
app = Act.app();
if (null != app) {
port = app.config().httpPort();
}
}
public void cache(String name, Object payload) {
cache.put(name, payload);
}
public Object cached(String name) {
return cache.get(name);
}
public int port() {
return port;
}
public void run(ProgressGauge gauge) {
current.set(this);
prepareHttp();
gauge.incrMaxHintBy(dependencies.size() + 2);
reset();
gauge.step();
proceed = runAll(gauge, dependencies);
if (proceed) {
proceed = runOne(target, gauge);
}
gauge.step();
}
private boolean reset() {
Collections.sort(dependencies, new ScenarioComparator(target.partition));
for (Scenario scenario : dependencies) {
scenario.reset();
}
target.reset();
clearSession();
return clearFixtures();
}
public void clearSession() {
cookieStore().clear();
}
public boolean clearFixtures() {
if (isTraceEnabled()) {
trace("clear fixture for " + target.name);
}
Timer timer = metric.startTimer("clear-fixtures");
try {
return verify(RequestSpec.RS_CLEAR_FIXTURE, "clearing fixtures");
} finally {
timer.stop();
}
}
private boolean runAll(ProgressGauge gauge, List scenarios) {
boolean proceed = true;
for (Scenario scenario : scenarios) {
proceed = proceed && runOne(scenario, gauge);
gauge.step();
if (!proceed) {
break;
}
}
return proceed;
}
private boolean runOne(Scenario scenario, ProgressGauge gauge) {
gauge.clearPayload();
gauge.setPayload(Test.PG_PAYLOAD_SCENARIO, scenario.title());
boolean okay;
try {
running = $.requireNotNull(scenario);
scenario.validate(this);
constants.putAll(running.constants);
okay = createFixtures() && scenario.run(this, gauge);
scenario.status = TestStatus.of(okay);
} catch (RuntimeException e) {
okay = false;
String message = e.getMessage();
scenario.errorMessage = S.blank(message) ? e.getClass().getName() : message;
scenario.cause = e.getCause();
scenario.status = TestStatus.FAIL;
}
gauge.setPayload(PG_PAYLOAD_FAILED, !scenario.status.pass());
return okay;
}
Response sendRequest(RequestSpec req) throws IOException {
Timer timer = metric.startTimer("build-request");
Request httpRequest = new RequestBuilder(req, this, port).build();
timer.stop();
timer = metric.startTimer("send-request");
Response resp = http.newCall(httpRequest).execute();
timer.stop();
lastHeaders.set(resp.headers());
return resp;
}
private boolean createFixtures() {
if (running.fixtures.isEmpty()) {
return true;
}
Timer timer = metric.startTimer("create-fixtures");
try {
RequestSpec req = RequestSpec.loadFixtures(running.fixtures);
return verify(req, "creating fixtures");
} finally {
timer.stop();
}
}
void verifyDownloadChecksum(Response response, String checksumExpected) {
if (checksumExpected.startsWith("${")) {
checksumExpected = S.string(evalStr(checksumExpected));
}
Object o = cached(checksumExpected);
if (null == o) {
o = cached("checksum-last");
}
if (null == o) {
o = checksumExpected;
}
ResponseBody body = response.body();
errorIf(null == body, "No download found");
String downloadChecksum = IO.checksum(body.byteStream());
errorIfNot($.eq(downloadChecksum, o), "Download checksum not match");
}
void verifyDownloadFilename(Response response, String filenameExpected) {
String header = response.header("Content-Disposition");
// header should be something like: attachment; filename="postcodes.xls"
errorIf(S.isBlank(header), "No Content-Disposition header found");
int pos = header.indexOf('"');
errorIf(pos < 0, "Content-Disposition header does not have filename specified: " + header);
String filename = header.substring(pos + 1);
filename = filename.substring(0, filename.length() - 1); // strip off last `"`
errorIfNot(S.eq(filenameExpected, filename), "Download filename[%s] not match expected value[%s]", filename, filenameExpected);
}
Scenario scenario() {
return null == running ? target : running;
}
boolean verify(RequestSpec req, String operation) {
boolean pass = true;
Response resp = null;
try {
resp = sendRequest(req);
if (!resp.isSuccessful()) {
pass = false;
scenario().errorMessage = "Fixtures loading failure";
}
return pass;
} catch (IOException e) {
scenario().errorMessage = "Error " + operation;
ErrorMessage.error(e, scenario().errorMessage);
return false;
} finally {
IO.close(resp);
}
}
private void prepareHttp() {
if (isTraceEnabled()) {
trace("prepareHTTP for scenario: " + target.name);
}
long timeout = app.config().testTimeout();
http = new OkHttpClient.Builder()
.cookieJar(cookieStore())
.connectTimeout(timeout, TimeUnit.SECONDS)
.readTimeout(timeout, TimeUnit.SECONDS)
.writeTimeout(timeout, TimeUnit.SECONDS)
.build();
}
private synchronized CookieStore cookieStore() {
if (null == cookieStore) {
App app = Act.app();
cookieStore = null == app ? new CookieStore() : app.getInstance(CookieStore.class);
}
return cookieStore;
}
String processStringSubstitution(String s) {
int n = s.indexOf("${");
if (n < 0) {
return s;
}
if (n == 0 && s.endsWith(")}")) {
s = s.substring(2);
s = s.substring(0, s.length() - 1);
return S.string(evalFunc(s));
}
int a = 0;
int z = n;
S.Buffer buf = S.buffer();
while (true) {
buf.append(s.substring(a, z));
n = s.indexOf("}", z);
a = n;
String part = s.substring(z + 2, a);
if (part.contains("(") && part.endsWith(")")) {
buf.append(evalFunc(part));
} else {
String key = part;
String payload = "";
if (part.contains(":")) {
S.Binary binary = S.binarySplit(part, ':');
key = binary.first();
payload = binary.second();
}
buf.append(getVal(key, payload));
}
n = s.indexOf("${", a);
if (n < 0) {
buf.append(s.substring(a + 1));
return buf.toString();
}
z = n;
}
}
Object evalFunc(String funcExpr) {
String funcName = funcExpr;
List vals = C.list();
if (funcExpr.contains("(")) {
funcName = S.cut(funcExpr).beforeFirst("(");
String paramStr = S.cut(funcExpr).afterFirst("(");
paramStr = S.cut(paramStr).beforeLast(")");
if (S.notBlank(paramStr)) {
if (S.is(paramStr).wrappedWith(S.DOUBLE_QUOTES) || S.is(paramStr).wrappedWith(S.SINGLE_QUOTES)) {
paramStr = paramStr.substring(1, paramStr.length() - 1);
vals = C.list(paramStr);
} else {
vals = C.newList(S.fastSplit(paramStr, ","));
for (int i = 0; i < vals.size(); ++i) {
String val = S.ensure(vals.get(i).trim()).strippedOff(S.DOUBLE_QUOTES);
val = processStringSubstitution(val);
vals.set(i, val);
}
}
}
}
Func func = $.convert(funcName).to(Func.class);
switch (vals.size()) {
case 0:
break;
case 1:
func.init(vals.get(0));
break;
default:
func.init(vals);
}
return func.apply();
}
Object eval(String expr) {
if (expr.startsWith("func:")) {
return evalFunc(expr.substring(5));
} else if (expr.contains("(")) {
return evalFunc(expr);
}
String key = S.underscore(expr);
Object o = constants.get(key);
o = null == o ? cache.get(key) : o;
o = null == o ? Test.constant(key) : o;
try {
// final try - treat it as a function
return null == o ? evalFunc(expr) : o;
} catch (IllegalArgumentException e) {
return null;
}
}
Object evalStr(String s) {
int n = s.indexOf("${");
if (n < 0) {
return s;
}
if (n > 0) {
return processStringSubstitution(s);
}
int z = s.indexOf("}");
if (z < s.length() - 1) {
return processStringSubstitution(s);
}
String part = s.substring(2, z);
if (part.contains("(") && part.endsWith(")")) {
return evalFunc(part);
} else {
String key = part;
String payload = "";
if (part.contains(":")) {
S.Binary binary = S.binarySplit(part, ':');
key = binary.first();
payload = binary.second();
}
return getVal(key, payload);
}
}
private Object getVal(String key) {
if ("last".equals(key)) {
return lastData.get();
}
Object o = cache.get(key);
if (null != o) {
return o;
}
if (key.contains(".")) {
String firstLevel = S.cut(key).beforeFirst(".");
Object firstLevelVal = cache.get(firstLevel);
if (null != firstLevelVal) {
try {
return $.getProperty(firstLevelVal, S.cut(key).afterFirst("."));
} catch (Exception e) {
// ignore
}
}
}
String underscoredKey = S.underscore(key);
o = constants.get(underscoredKey);
if (null != o) {
return o;
}
o = Test.constant(underscoredKey);
if (null != o) {
return o;
}
try {
return evalFunc(key);
} catch (Exception e) {
try {
return getLastVal(key);
} catch (Exception e1) {
throw E.unexpected("Unable to get value by key: %s", key);
}
}
}
private Object getVal(String key, String ref) {
Object stuff = getVal(key);
if (S.blank(ref) || null == stuff) {
return stuff;
}
if (stuff instanceof String) {
String str = $.cast(stuff);
if (str.contains("<") && str.contains(">")) {
// try get html element
try {
Document doc = Jsoup.parse(str, S.concat("http://localhost:", port, "/"));
Elements elements = doc.select(ref);
if (elements.size() != 0) {
return elements.get(0).text();
}
} catch (Exception e) {
// just ignore
}
}
}
return JSONTraverser.traverse(stuff, ref);
}
Object getLastVal(String ref) {
return getVal("last", ref);
}
void verifyBody(String bodyString, ResponseSpec spec) {
lastData.set(bodyString);
if (null == spec) {
if (bodyString.startsWith("[")) {
JSONArray array = JSON.parseArray(bodyString);
lastData.set(array);
} else if (bodyString.startsWith("{")) {
JSONObject obj = JSON.parseObject(bodyString);
lastData.set(obj);
}
return;
}
if (null != spec.text) {
verifyValue("body text", bodyString, spec.text);
} else if (null != spec.json && !spec.json.isEmpty()) {
if (bodyString.startsWith("[")) {
JSONArray array = JSON.parseArray(bodyString);
lastData.set(array);
verifyList("body json array", array, spec.json);
} else if (bodyString.startsWith("{")) {
JSONObject obj = JSON.parseObject(bodyString);
lastData.set(obj);
verifyJsonObject(obj, spec.json);
} else {
ErrorMessage.error("Unknown JSON string: \n%s", bodyString);
}
} else if (null != spec.xml && !spec.xml.isEmpty()) {
org.w3c.dom.Document doc = XML.read(bodyString);
JSONObject obj = $.convert(doc).to(JSONObject.class);
String xmlRoot = app.config().xmlRootTag();
if (obj.containsKey(xmlRoot)) {
Object o = obj.get(xmlRoot);
lastData.set(o);
if (o instanceof JSONObject) {
obj = (JSONObject) o;
verifyJsonObject(obj, spec.xml);
} else if (o instanceof List) {
verifyList("body xml array", (List)o, spec.xml);
} else {
throw new UnexpectedException("Unknown root type: " + o.getClass());
}
} else {
lastData.set(obj);
verifyJsonObject(obj, spec.xml);
}
} else if (null != spec.html && !spec.html.isEmpty()) {
lastData.set(bodyString);
Document doc = Jsoup.parse(bodyString, S.concat("http://localhost:", port, "/"));
for (Map.Entry entry : spec.html.entrySet()) {
String path = entry.getKey();
Elements elements = doc.select(path);
verifyValue("body html path", elements, entry.getValue());
}
}
}
void verifyList(String name, List array, Map spec) {
for (Object obj : spec.entrySet()) {
Map.Entry entry = $.cast(obj);
Object key = entry.getKey();
String sKey = S.string(key);
Object test = entry.getValue();
Object value = null;
if ("me".equals(key) || "this".equals(key)) {
value = array;
} else if ("size".equals(key) || "len".equals(key) || "length".equals(key)) {
value = array.size();
} else if ("toString".equals(key) || "string".equals(key) || "str".equals(key)) {
value = JSON.toJSONString(array);
} else if ("?".equals(key) || (sKey.toLowerCase().startsWith(""))) {
for (Object arrayElement : array) {
try {
verifyValue(name, arrayElement, test);
return;
} catch (Exception e) {
// try next one
}
}
throw ErrorMessage.error("No element matches requirement: %s", test);
} else if (S.isInt(sKey)) {
int id = Integer.parseInt(sKey);
value = array.get(id);
} else {
if (sKey.contains(".")) {
String id = S.cut(key).beforeFirst(".");
String prop = S.cut(key).afterFirst(".");
if ("?".equals(id) || "".equalsIgnoreCase(id)) {
for (Object arrayElement : array) {
if (!(arrayElement instanceof JSONObject)) {
continue;
}
try {
verifyValue(prop, ((JSONObject) arrayElement).get(prop), test);
return;
} catch (Exception e) {
// try next one
}
}
} else if (S.isInt(id)) {
int i = Integer.parseInt(id);
Object o = array.get(i);
if (o instanceof JSONObject) {
JSONObject json = (JSONObject) o;
value = json.get(prop);
}
}
}
if (null == value) {
throw ErrorMessage.error("Unknown attribute of array verification: %s", key);
}
}
verifyValue(name, value, test);
}
}
void verifyJsonObject(JSONObject obj, Map jsonSpec) {
for (Map.Entry entry : jsonSpec.entrySet()) {
String key = entry.getKey();
// ${sizeOf(date)}
if (key.startsWith("${")) {
// sizeOf(date)}
String s = key.substring(2);
// sizeOf(date)
s = s.substring(0, s.length() - 1);
// sizeOf
String funcName = s.substring(0, s.indexOf("("));
// date)
s = s.substring(s.indexOf("(") + 1);
// date
String varName = s.substring(0, s.length() - 1);
Object val = $.getProperty(obj, varName);
if (val instanceof CharSequence) {
val = S.wrap(val).with(S.DOUBLE_QUOTES);
}
val = evalFunc(funcName + "(" + val + ")");
verifyValue(key, val, entry.getValue());
} else {
Object val = $.getProperty(obj, key);
verifyValue(key, val, entry.getValue());
}
}
}
void verifyValue(String name, Object value, Object test) {
if (test instanceof List) {
verifyValue_(name, value, (List) test);
} else if (value instanceof List && test instanceof Map) {
verifyList(name, (List) value, (Map) test);
} else {
if (matches(value, test)) {
return;
}
if (value instanceof JSONObject) {
errorIfNot(test instanceof Map, "Cannot verify %s value [%s] against test [%s]", name, value, test);
JSONObject json = (JSONObject) value;
Map testMap = (Map) test;
for (Map.Entry, ?> entry : testMap.entrySet()) {
Object testKey = entry.getKey();
Object testValue = entry.getValue();
Object attr = json.get(testKey);
verifyValue(S.concat(name, ".", testKey), attr, testValue);
}
} else if (value instanceof Elements) {
if (test instanceof Map) {
verifyList(name, (Elements) value, (Map) test);
} else {
Elements elements = (Elements) value;
if (elements.isEmpty()) {
value = null;
} else {
value = elements.first();
}
verifyValue(name, value, test);
}
} else if (value instanceof Long) {
Long lng = (Long) value;
Long expected = null;
if (test instanceof Long) {
expected = (Long) test;
} else {
String s = S.string(test);
s = S.isIntOrLong(s) ? s : processStringSubstitution(s);
ErrorMessage.errorIfNot(S.isIntOrLong(s), "Cannot verify %s value [%s] against test", name, value, test);
expected = $.convert(s).toLong();
}
ErrorMessage.errorIfNot(lng.equals(expected), "Cannot verify %s value [%s] against test [%s]", name, value, test);
} else if (value instanceof Integer) {
Integer integer = (Integer) value;
Integer expected = null;
if (test instanceof Integer) {
expected = (Integer) test;
} else {
String s = S.string(test);
s = S.isInt(s) ? s : processStringSubstitution(s);
ErrorMessage.errorIfNot(S.isInt(s), "Cannot verify %s value [%s] against test", name, value, test);
expected = $.convert(s).toInteger();
}
ErrorMessage.errorIfNot(integer.equals(expected), "Cannot verify %s value [%s] against test [%s]", name, value, test);
} else if (value instanceof Number) {
Number found = (Number) value;
Number expected = null;
if (test instanceof Number) {
expected = (Number) test;
} else {
String s = S.string(test);
s = S.isNumeric(s) ? s : processStringSubstitution(s);
ErrorMessage.errorIfNot(S.isNumeric(S.string(s)), "Cannot verify %s value [%s] against test [%s]", name, value, test);
expected = $.convert(s).to(Double.class);
}
double delta = Math.abs(expected.doubleValue() - found.doubleValue());
if ((delta / found.doubleValue()) > 0.001) {
ErrorMessage.error("Cannot verify %s value [%s] against test [%s]", name, value, test);
}
} else {
// try convert the test into String
String testString = $.convert(test).toString();
boolean verified = verifyStringValue_(testString, value, test);
if (!verified) {
String processedString = processStringSubstitution(testString);
if (S.neq(processedString, testString)) {
verified = verifyStringValue_(processedString, value, test);
}
}
errorIfNot(verified, "Cannot verify %s value [%s] against test [%s]", name, value, test);
}
}
}
private boolean verifyStringValue_(String testString, Object value, Object test) {
if (matches(testString, value)) {
return true;
}
if (null != value && ("*".equals(test) || "...".equals(test) || "".equals(test))) {
return true;
}
try {
Pattern p = Pattern.compile(testString);
return p.matcher(S.string(value)).matches();
} catch (Exception e) {
// ignore
}
Verifier v = tryLoadVerifier(testString);
if (null != v && v.verify(value)) {
return true;
}
return false;
}
private void verifyValue_(String name, Object value, List tests) {
// try to do the literal match
if (value instanceof List) {
List found = (List) value;
boolean ok = found.size() == tests.size();
if (ok) {
for (int i = 0; i < found.size(); ++i) {
Object foundElement = found.get(i);
Object testElement = tests.get(i);
if (!matches(foundElement, testElement)) {
ok = false;
break;
}
}
}
if (ok) {
return;
}
}
// now try verifiers
if (value instanceof Elements) {
Elements elements = (Elements) value;
if (elements.size() > 0) {
value = elements.first();
} else {
value = null;
}
}
for (Object test : tests) {
errorIfNot(test instanceof Map, "Cannot verify %s value [%s] against test [%s]", name, value, test);
Map, ?> map = (Map) test;
errorIfNot(map.size() == 1, "Cannot verify %s value [%s] against test [%s]", name, value, test);
Map.Entry entry = map.entrySet().iterator().next();
Object entryValue = entry.getValue();
if (entryValue instanceof String) {
String s = (String) entryValue;
Object processed = evalStr(s);
if (s != processed) {
entry.setValue(processed);
}
}
Verifier v = $.convert(map).to(Verifier.class);
errorIf(null == v, "Cannot verify %s value [%s] against test [%s]", name, value, test);
errorIf(!verify(v, value), "Cannot verify %s value [%s] against test [%s]", name, value, v);
}
}
private boolean verify(Verifier test, Object value) {
if (test.verify(value)) {
return true;
}
if (value instanceof Element) {
Element e = (Element) value;
if (test.verify(e.val())) {
return true;
}
if (test.verify(e.text())) {
return true;
}
if (test.verify(e.html())) {
return true;
}
}
return false;
}
private static boolean matches(Object a, Object b) {
if ($.eq(a, b)) {
return true;
}
if (!((b instanceof String) && (a instanceof Element))) {
return false;
}
String test = S.string(b);
Element element = (Element) a;
// try html
String html = element.html();
if (S.eq(html, test, S.IGNORECASE)) {
return true;
}
// try text
String text = element.text();
if (S.eq(text, test, S.IGNORECASE)) {
return true;
}
// try val
String val = element.val();
if (S.eq(val, test, S.IGNORECASE)) {
return true;
}
return false;
}
private Class> tryLoadClass(String name) {
try {
return null != app ? app.classForName(name) : $.classForName(name);
} catch (Exception e) {
return null;
}
}
private Verifier tryLoadVerifier(String name) {
boolean revert = false;
if (name.startsWith("!") || name.startsWith("-")) {
revert = true;
name = name.substring(1).trim();
} else if (name.startsWith("not:") || name.startsWith("not ")) {
revert = true;
name = name.substring(4).trim();
}
Class> c = tryLoadClass(name);
if (null != c) {
if (Verifier.class.isAssignableFrom(c)) {
final Verifier v = (Verifier) (null != app ? app.getInstance(c) : $.newInstance(c));
return v.meOrReversed(revert);
} else {
throw new UnexpectedException("Class not supported: " + name);
}
}
return null;
}
}