mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 07:18:39 +09:00
Merge remote-tracking branch 'origin/master' into custom
This commit is contained in:
commit
f9d748b5f0
23946 changed files with 541 additions and 1480141 deletions
|
|
@ -90,9 +90,6 @@ SEARCH_PATHS = [
|
|||
'testing/mozbase/manifestparser',
|
||||
'testing/taskcluster',
|
||||
'testing/tools/autotry',
|
||||
'testing/web-platform',
|
||||
'testing/web-platform/harness',
|
||||
'testing/web-platform/tests/tools/wptserve',
|
||||
'testing/xpcshell',
|
||||
'xpcom/idl-parser',
|
||||
]
|
||||
|
|
@ -121,7 +118,6 @@ MACH_MODULES = [
|
|||
'testing/mochitest/mach_commands.py',
|
||||
'testing/mozharness/mach_commands.py',
|
||||
'testing/talos/mach_commands.py',
|
||||
'testing/web-platform/mach_commands.py',
|
||||
'testing/xpcshell/mach_commands.py',
|
||||
'tools/docs/mach_commands.py',
|
||||
'tools/lint/mach_commands.py',
|
||||
|
|
|
|||
|
|
@ -1166,3 +1166,24 @@ function RegExpStringIteratorNext() {
|
|||
result.value = match;
|
||||
return result;
|
||||
}
|
||||
|
||||
// String.prototype.matchAll proposal.
|
||||
// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
|
||||
// 7.2.8 IsRegExp ( argument )
|
||||
function IsRegExp(argument) {
|
||||
// Step 1.
|
||||
if (!IsObject(argument)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2.
|
||||
var matcher = argument[std_match];
|
||||
|
||||
// Step 3.
|
||||
if (matcher !== undefined) {
|
||||
return !!matcher;
|
||||
}
|
||||
|
||||
// Steps 4-5.
|
||||
return IsPossiblyWrappedRegExpObject(argument);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,6 +224,115 @@ function String_generic_replace(thisValue, searchValue, replaceValue) {
|
|||
return callFunction(String_replace, thisValue, searchValue, replaceValue);
|
||||
}
|
||||
|
||||
function String_replaceAll(searchValue, replaceValue) {
|
||||
|
||||
// Step 1.
|
||||
RequireObjectCoercible(this);
|
||||
|
||||
// Step 2.
|
||||
if (searchValue !== undefined && searchValue !== null) {
|
||||
// Steps 2.a-b.
|
||||
if (IsRegExp(searchValue)) {
|
||||
// Step 2.b.i.
|
||||
var flags = searchValue.flags;
|
||||
|
||||
// Step 2.b.ii.
|
||||
if (flags === undefined || flags === null) {
|
||||
ThrowTypeError(JSMSG_FLAGS_UNDEFINED_OR_NULL);
|
||||
}
|
||||
|
||||
// Step 2.b.iii.
|
||||
if (!callFunction(std_String_includes, ToString(flags), "g")) {
|
||||
ThrowTypeError(JSMSG_REQUIRES_GLOBAL_REGEXP, "replaceAll");
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2.c.
|
||||
var replacer = GetMethod(searchValue, std_replace);
|
||||
|
||||
// Step 2.b.
|
||||
if (replacer !== undefined) {
|
||||
return callContentFunction(replacer, searchValue, this, replaceValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
var string = ToString(this);
|
||||
|
||||
// Step 4.
|
||||
var searchString = ToString(searchValue);
|
||||
|
||||
// Steps 5-6.
|
||||
if (!IsCallable(replaceValue)) {
|
||||
// Steps 7-16.
|
||||
return StringReplaceAllString(string, searchString, ToString(replaceValue));
|
||||
}
|
||||
|
||||
// Step 7.
|
||||
var searchLength = searchString.length;
|
||||
|
||||
// Step 8.
|
||||
var advanceBy = std_Math_max(1, searchLength);
|
||||
|
||||
// Step 9 (not needed in this implementation).
|
||||
|
||||
// Step 12.
|
||||
var endOfLastMatch = 0;
|
||||
|
||||
// Step 13.
|
||||
var result = "";
|
||||
|
||||
// Steps 10-11, 14.
|
||||
var position = 0;
|
||||
while (true) {
|
||||
// Steps 10-11.
|
||||
//
|
||||
// StringIndexOf doesn't clamp the |position| argument to the input
|
||||
// string length, i.e. |StringIndexOf("abc", "", 4)| returns -1,
|
||||
// whereas |"abc".indexOf("", 4)| returns 3. That means we need to
|
||||
// exit the loop when |nextPosition| is smaller than |position| and
|
||||
// not just when |nextPosition| is -1.
|
||||
var nextPosition = callFunction(std_String_indexOf, string, searchString, position);
|
||||
if (nextPosition < position) {
|
||||
break;
|
||||
}
|
||||
position = nextPosition;
|
||||
|
||||
// Step 14.a.
|
||||
var replacement = ToString(callContentFunction(replaceValue, undefined, searchString,
|
||||
position, string));
|
||||
|
||||
// Step 14.b (not applicable).
|
||||
|
||||
// Step 14.c.
|
||||
var stringSlice = Substring(string, endOfLastMatch, position - endOfLastMatch);
|
||||
|
||||
// Step 14.d.
|
||||
result += stringSlice + replacement;
|
||||
|
||||
// Step 14.e.
|
||||
endOfLastMatch = position + searchLength;
|
||||
|
||||
// Step 11.b.
|
||||
position += advanceBy;
|
||||
}
|
||||
|
||||
// Step 15.
|
||||
if (endOfLastMatch < string.length) {
|
||||
// Step 15.a.
|
||||
result += Substring(string, endOfLastMatch, string.length - endOfLastMatch);
|
||||
}
|
||||
|
||||
// Step 16.
|
||||
return result;
|
||||
}
|
||||
|
||||
function String_generic_replace(thisValue, searchValue, replaceValue) {
|
||||
if (thisValue === undefined)
|
||||
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.replace');
|
||||
return callFunction(String_replace, thisValue, searchValue, replaceValue);
|
||||
}
|
||||
|
||||
function StringProtoHasNoSearch() {
|
||||
var ObjectProto = GetBuiltinPrototype("Object");
|
||||
var StringProto = GetBuiltinPrototype("String");
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@
|
|||
_(RegExpSearcher) \
|
||||
_(RegExpTester) \
|
||||
_(IsRegExpObject) \
|
||||
_(IntrinsicIsPossiblyWrappedRegExpObject)\
|
||||
_(RegExpPrototypeOptimizable) \
|
||||
_(RegExpInstanceOptimizable) \
|
||||
_(GetFirstDollarIndex) \
|
||||
|
|
|
|||
|
|
@ -867,6 +867,7 @@ class IonBuilder
|
|||
InliningStatus inlineRegExpSearcher(CallInfo& callInfo);
|
||||
InliningStatus inlineRegExpTester(CallInfo& callInfo);
|
||||
InliningStatus inlineIsRegExpObject(CallInfo& callInfo);
|
||||
InliningStatus inlineIsPossiblyWrappedRegExpObject(CallInfo& callInfo);
|
||||
InliningStatus inlineRegExpPrototypeOptimizable(CallInfo& callInfo);
|
||||
InliningStatus inlineRegExpInstanceOptimizable(CallInfo& callInfo);
|
||||
InliningStatus inlineGetFirstDollarIndex(CallInfo& callInfo);
|
||||
|
|
|
|||
|
|
@ -185,6 +185,8 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target)
|
|||
return inlineRegExpTester(callInfo);
|
||||
case InlinableNative::IsRegExpObject:
|
||||
return inlineIsRegExpObject(callInfo);
|
||||
case InlinableNative::IntrinsicIsPossiblyWrappedRegExpObject:
|
||||
return inlineIsPossiblyWrappedRegExpObject(callInfo);
|
||||
case InlinableNative::RegExpPrototypeOptimizable:
|
||||
return inlineRegExpPrototypeOptimizable(callInfo);
|
||||
case InlinableNative::RegExpInstanceOptimizable:
|
||||
|
|
@ -1902,6 +1904,44 @@ IonBuilder::inlineIsRegExpObject(CallInfo& callInfo)
|
|||
callInfo.setImplicitlyUsedUnchecked();
|
||||
return InliningStatus_Inlined;
|
||||
}
|
||||
IonBuilder::InliningStatus
|
||||
IonBuilder::inlineIsPossiblyWrappedRegExpObject(CallInfo& callInfo)
|
||||
{
|
||||
MOZ_ASSERT(!callInfo.constructing());
|
||||
MOZ_ASSERT(callInfo.argc() == 1);
|
||||
|
||||
if (callInfo.getArg(0)->type() != MIRType::Object)
|
||||
return InliningStatus_NotInlined;
|
||||
if (getInlineReturnType() != MIRType::Boolean)
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
MDefinition* arg = callInfo.getArg(0);
|
||||
if (arg->type() != MIRType::Object) {
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
TemporaryTypeSet* types = arg->resultTypeSet();
|
||||
if (!types) {
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
// Don't inline if the argument might be a wrapper.
|
||||
if (types->forAllClasses(constraints(), IsProxyClass) !=
|
||||
TemporaryTypeSet::ForAllResult::ALL_FALSE) {
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
if (const Class* clasp = types->getKnownClass(constraints())) {
|
||||
pushConstant(BooleanValue(clasp == &RegExpObject::class_));
|
||||
} else {
|
||||
MHasClass* hasClass = MHasClass::New(alloc(), arg, &RegExpObject::class_);
|
||||
current->add(hasClass);
|
||||
current->push(hasClass);
|
||||
}
|
||||
|
||||
callInfo.setImplicitlyUsedUnchecked();
|
||||
return InliningStatus_Inlined;
|
||||
}
|
||||
|
||||
IonBuilder::InliningStatus
|
||||
IonBuilder::inlineRegExpPrototypeOptimizable(CallInfo& callInfo)
|
||||
|
|
|
|||
261
js/src/jsstr.cpp
261
js/src/jsstr.cpp
|
|
@ -2111,9 +2111,13 @@ AppendDollarReplacement(StringBuffer& newReplaceChars, size_t firstDollarIndex,
|
|||
const CharT* repChars, size_t repLength)
|
||||
{
|
||||
MOZ_ASSERT(firstDollarIndex < repLength);
|
||||
MOZ_ASSERT(matchStart <= matchLimit);
|
||||
MOZ_ASSERT(matchLimit <= text->length());
|
||||
|
||||
/* Move the pre-dollar chunk in bulk. */
|
||||
newReplaceChars.infallibleAppend(repChars, firstDollarIndex);
|
||||
if (!newReplaceChars.append(repChars, firstDollarIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Move the rest char-by-char, interpreting dollars as we encounter them. */
|
||||
const CharT* repLimit = repChars + repLength;
|
||||
|
|
@ -2329,6 +2333,200 @@ js::str_flat_replace_string(JSContext *cx, HandleString string, HandleString pat
|
|||
return str;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-string-replaceall/#sec-string.prototype.replaceall
|
||||
// Steps 7-16 when functionalReplace is false and searchString is not empty.
|
||||
//
|
||||
// The steps are quite different, for performance. Loops in steps 11 and 14
|
||||
// are fused. GetSubstitution is optimized away when possible.
|
||||
template <typename StrChar, typename RepChar>
|
||||
static JSString* ReplaceAll(JSContext* cx, JSLinearString* str,
|
||||
JSLinearString* pat,
|
||||
JSLinearString* rep) {
|
||||
// Step 7.
|
||||
const size_t stringLength = str->length();
|
||||
const size_t searchLength = pat->length();
|
||||
const size_t replaceLength = rep->length();
|
||||
|
||||
MOZ_ASSERT(stringLength > 0);
|
||||
MOZ_ASSERT(searchLength > 0);
|
||||
MOZ_ASSERT(stringLength >= searchLength);
|
||||
|
||||
// Step 8 (advanceBy is equal to searchLength when searchLength > 0).
|
||||
|
||||
// Step 9 (not needed in this implementation).
|
||||
|
||||
// Step 10.
|
||||
// Find the first match.
|
||||
int32_t position = StringMatch(str, pat, 0);
|
||||
|
||||
// Nothing to replace, so return early.
|
||||
if (position < 0) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// Step 11 (moved below).
|
||||
|
||||
// Step 12.
|
||||
uint32_t endOfLastMatch = 0;
|
||||
|
||||
// Step 13.
|
||||
StringBuffer result(cx);
|
||||
if (std::is_same<StrChar, char16_t>::value ||
|
||||
std::is_same<RepChar, char16_t>::value) {
|
||||
if (!result.ensureTwoByteChars()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
AutoCheckCannotGC nogc;
|
||||
const StrChar* strChars = str->chars<StrChar>(nogc);
|
||||
const RepChar* repChars = rep->chars<RepChar>(nogc);
|
||||
|
||||
uint32_t dollarIndex = FindDollarIndex(repChars, replaceLength);
|
||||
|
||||
// If it's true, we are sure that the result's length is, at least, the same
|
||||
// length as |str->length()|.
|
||||
if (replaceLength >= searchLength) {
|
||||
if (!result.reserve(stringLength)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
// Step 14.c.
|
||||
// Append the substring before the current match.
|
||||
if (!result.append(strChars + endOfLastMatch,
|
||||
position - endOfLastMatch)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Steps 14.a-b and 14.d.
|
||||
// Append the replacement.
|
||||
if (dollarIndex != UINT32_MAX) {
|
||||
size_t matchLimit = position + searchLength;
|
||||
if (!AppendDollarReplacement(result, dollarIndex, position, matchLimit,
|
||||
str, repChars, replaceLength)) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
if (!result.append(repChars, replaceLength)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 14.e.
|
||||
endOfLastMatch = position + searchLength;
|
||||
|
||||
// Step 11.
|
||||
// Find the next match.
|
||||
position = StringMatch(str, pat, endOfLastMatch);
|
||||
} while (position >= 0);
|
||||
|
||||
// Step 15.
|
||||
// Append the substring after the last match.
|
||||
if (!result.append(strChars + endOfLastMatch,
|
||||
stringLength - endOfLastMatch)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 16.
|
||||
return result.finishString();
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-string-replaceall/#sec-string.prototype.replaceall
|
||||
// Steps 7-16 when functionalReplace is false and searchString is the empty
|
||||
// string.
|
||||
//
|
||||
// The steps are quite different, for performance. Loops in steps 11 and 14
|
||||
// are fused. GetSubstitution is optimized away when possible.
|
||||
template <typename StrChar, typename RepChar>
|
||||
static JSString* ReplaceAllInterleave(JSContext* cx, JSLinearString* str,
|
||||
JSLinearString* rep) {
|
||||
// Step 7.
|
||||
const size_t stringLength = str->length();
|
||||
const size_t replaceLength = rep->length();
|
||||
|
||||
// Step 8 (advanceBy is 1 when searchString is the empty string).
|
||||
|
||||
// Steps 9-12 (trivial when searchString is the empty string).
|
||||
|
||||
// Step 13.
|
||||
StringBuffer result(cx);
|
||||
if (std::is_same<StrChar, char16_t>::value ||
|
||||
std::is_same<RepChar, char16_t>::value) {
|
||||
if (!result.ensureTwoByteChars()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
AutoCheckCannotGC nogc;
|
||||
const StrChar* strChars = str->chars<StrChar>(nogc);
|
||||
const RepChar* repChars = rep->chars<RepChar>(nogc);
|
||||
|
||||
uint32_t dollarIndex = FindDollarIndex(repChars, replaceLength);
|
||||
|
||||
if (dollarIndex != UINT32_MAX) {
|
||||
if (!result.reserve(stringLength)) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// Compute the exact result length when no substitutions take place.
|
||||
CheckedInt<uint32_t> strLength(stringLength);
|
||||
CheckedInt<uint32_t> repLength(replaceLength);
|
||||
CheckedInt<uint32_t> length = strLength + (strLength + 1) * repLength;
|
||||
if (!length.isValid()) {
|
||||
ReportAllocationOverflow(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!result.reserve(length.value())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto appendReplacement = [&](size_t match) {
|
||||
if (dollarIndex != UINT32_MAX) {
|
||||
return AppendDollarReplacement(result, dollarIndex, match, match,
|
||||
str, repChars, replaceLength);
|
||||
}
|
||||
return result.append(repChars, replaceLength);
|
||||
};
|
||||
|
||||
for (size_t index = 0; index < stringLength; index++) {
|
||||
// Steps 11, 14.a-b and 14.d.
|
||||
// The empty string matches before each character.
|
||||
if (!appendReplacement(index)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 14.c.
|
||||
if (!result.append(strChars[index])) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Steps 11, 14.a-b and 14.d.
|
||||
// The empty string also matches at the end of the string.
|
||||
if (!appendReplacement(stringLength)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 15 (not applicable when searchString is the empty string).
|
||||
}
|
||||
|
||||
// Step 16.
|
||||
return result.finishString();
|
||||
}
|
||||
|
||||
// String.prototype.replaceAll (Stage 3 proposal)
|
||||
// https://tc39.es/proposal-string-replaceall/
|
||||
//
|
||||
// String.prototype.replaceAll ( searchValue, replaceValue )
|
||||
//
|
||||
// Steps 7-16 when functionalReplace is false.o
|
||||
JSString*
|
||||
js::str_replace_string_raw(JSContext* cx, HandleString string, HandleString pattern,
|
||||
HandleString replacement)
|
||||
|
|
@ -2371,6 +2569,62 @@ js::str_replace_string_raw(JSContext* cx, HandleString string, HandleString patt
|
|||
return BuildFlatReplacement(cx, string, repl, match, patternLength);
|
||||
}
|
||||
|
||||
JSString*
|
||||
js::str_replaceAll_string_raw(JSContext* cx, HandleString string, HandleString pattern,
|
||||
HandleString replacement)
|
||||
{
|
||||
const size_t stringLength = string->length();
|
||||
const size_t searchLength = pattern->length();
|
||||
|
||||
// Directly return when we're guaranteed to find no match.
|
||||
if (searchLength > stringLength) {
|
||||
return string;
|
||||
}
|
||||
|
||||
RootedLinearString str(cx, string->ensureLinear(cx));
|
||||
if (!str) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedLinearString repl(cx, replacement->ensureLinear(cx));
|
||||
if (!repl) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedLinearString search(cx, pattern->ensureLinear(cx));
|
||||
if (!search) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The pattern is empty, so we interleave the replacement string in-between
|
||||
// each character.
|
||||
if (searchLength == 0) {
|
||||
if (str->hasTwoByteChars()) {
|
||||
if (repl->hasTwoByteChars()) {
|
||||
return ReplaceAllInterleave<char16_t, char16_t>(cx, str, repl);
|
||||
}
|
||||
return ReplaceAllInterleave<char16_t, Latin1Char>(cx, str, repl);
|
||||
}
|
||||
if (repl->hasTwoByteChars()) {
|
||||
return ReplaceAllInterleave<Latin1Char, char16_t>(cx, str, repl);
|
||||
}
|
||||
return ReplaceAllInterleave<Latin1Char, Latin1Char>(cx, str, repl);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(stringLength > 0);
|
||||
|
||||
if (str->hasTwoByteChars()) {
|
||||
if (repl->hasTwoByteChars()) {
|
||||
return ReplaceAll<char16_t, char16_t>(cx, str, search, repl);
|
||||
}
|
||||
return ReplaceAll<char16_t, Latin1Char>(cx, str, search, repl);
|
||||
}
|
||||
if (repl->hasTwoByteChars()) {
|
||||
return ReplaceAll<Latin1Char, char16_t>(cx, str, search, repl);
|
||||
}
|
||||
return ReplaceAll<Latin1Char, Latin1Char>(cx, str, search, repl);
|
||||
}
|
||||
|
||||
// ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
|
||||
static JSObject*
|
||||
SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearString sep,
|
||||
|
|
@ -2572,9 +2826,9 @@ static const JSFunctionSpec string_methods[] = {
|
|||
JS_FN("endsWith", str_endsWith, 1,0),
|
||||
JS_FN("trim", str_trim, 0,0),
|
||||
JS_FN("trimLeft", str_trimStart, 0,0),
|
||||
JS_FN("trimStart", str_trimStart, 0,0),
|
||||
JS_FN("trimStart", str_trimStart, 0,0),
|
||||
JS_FN("trimRight", str_trimEnd, 0,0),
|
||||
JS_FN("trimEnd", str_trimEnd, 0,0),
|
||||
JS_FN("trimEnd", str_trimEnd, 0,0),
|
||||
JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,0),
|
||||
JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,0),
|
||||
JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
|
||||
|
|
@ -2586,6 +2840,7 @@ static const JSFunctionSpec string_methods[] = {
|
|||
JS_SELF_HOSTED_FN("matchAll", "String_matchAll", 1,0),
|
||||
JS_SELF_HOSTED_FN("search", "String_search", 1,0),
|
||||
JS_SELF_HOSTED_FN("replace", "String_replace", 2,0),
|
||||
JS_SELF_HOSTED_FN("replaceAll", "String_replaceAll", 2,0),
|
||||
JS_SELF_HOSTED_FN("split", "String_split", 2,0),
|
||||
JS_SELF_HOSTED_FN("substr", "String_substr", 2,0),
|
||||
|
||||
|
|
|
|||
|
|
@ -476,6 +476,10 @@ JSString*
|
|||
str_replace_string_raw(JSContext* cx, HandleString string, HandleString pattern,
|
||||
HandleString replacement);
|
||||
|
||||
JSString*
|
||||
str_replaceAll_string_raw(JSContext* cx, HandleString string, HandleString pattern,
|
||||
HandleString replacement);
|
||||
|
||||
extern bool
|
||||
StringConstructor(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
|
|
|
|||
63
js/src/tests/ecma_2021/manual/test-replaceall.html
Normal file
63
js/src/tests/ecma_2021/manual/test-replaceall.html
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<html><head>
|
||||
<script>
|
||||
function passfail(test) {
|
||||
if (test) {
|
||||
document.writeln('<span style="color:#008000;">PASS</span>');
|
||||
} else {
|
||||
document.writeln('<span style="color:#BF0000;">FAIL</span>');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Replace "a" with "b":
|
||||
<script>
|
||||
var input='1a234abcd';
|
||||
var match='a';
|
||||
var expected='1b234bbcd';
|
||||
passfail(input.replaceAll(match,'b') === expected);
|
||||
</script><br>
|
||||
Replace "a" with "aa":
|
||||
<script>
|
||||
var input='aabbccdd';
|
||||
var match='a';
|
||||
var expected='aaaabbccdd';
|
||||
passfail(input.replaceAll(match,'aa') === expected);
|
||||
</script><br>
|
||||
Replace "" with "b":
|
||||
<script>
|
||||
var input='aaaa';
|
||||
var match='';
|
||||
var expected='babababab';
|
||||
passfail(input.replaceAll(match,'b') === expected);
|
||||
</script><br>
|
||||
Replace "old" with special pattern "new (was: $&)":
|
||||
<script>
|
||||
var input='This is the old thing of the old time.';
|
||||
var match='old';
|
||||
var expected='This is the new (was: old) thing of the new (was: old) time.';
|
||||
passfail(input.replaceAll(match,'new (was: $&)') === expected);
|
||||
</script><br>
|
||||
Replace "/[0-9]/g" with "b":
|
||||
<script>
|
||||
var input='1a234abcd';
|
||||
var match=/[0-9]/g;
|
||||
var expected='babbbabcd';
|
||||
passfail(input.replaceAll(match,'b') === expected);
|
||||
</script><br>
|
||||
Replace "/[0-9]/" with "b" (Throws):
|
||||
<script>
|
||||
var input='1a234abcd';
|
||||
var match=/[0-9]/;
|
||||
var expected='1a234abcd';
|
||||
try {
|
||||
test=input.replaceAll(match,'b');
|
||||
passfail(false);
|
||||
} catch(e) {
|
||||
passfail(true);
|
||||
document.writeln('('+e+')');
|
||||
}
|
||||
</script><br>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -189,6 +189,28 @@ intrinsic_IsInstanceOfBuiltin(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool
|
||||
intrinsic_IsPossiblyWrappedBuiltin(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
|
||||
bool isTypeT = false;
|
||||
if (args[0].isObject()) {
|
||||
JSObject* obj = CheckedUnwrap(&args[0].toObject());
|
||||
if (!obj) {
|
||||
JS_ReportErrorASCII(cx, "Permission denied to access object");
|
||||
return false;
|
||||
}
|
||||
|
||||
isTypeT = obj->is<T>();
|
||||
}
|
||||
|
||||
args.rval().setBoolean(isTypeT);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool
|
||||
intrinsic_GuardToBuiltin(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
|
@ -1172,27 +1194,6 @@ intrinsic_IsFloat32TypedArray(JSContext* cx, unsigned argc, Value* vp)
|
|||
return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Float32);
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_IsPossiblyWrappedTypedArray(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
|
||||
bool isTypedArray = false;
|
||||
if (args[0].isObject()) {
|
||||
JSObject* obj = CheckedUnwrap(&args[0].toObject());
|
||||
if (!obj) {
|
||||
JS_ReportErrorASCII(cx, "Permission denied to access object");
|
||||
return false;
|
||||
}
|
||||
|
||||
isTypedArray = obj->is<TypedArrayObject>();
|
||||
}
|
||||
|
||||
args.rval().setBoolean(isTypedArray);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_TypedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
|
|
@ -1756,6 +1757,24 @@ intrinsic_StringReplaceString(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_StringReplaceAllString(JSContext * cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 3);
|
||||
|
||||
RootedString string(cx, args[0].toString());
|
||||
RootedString pattern(cx, args[1].toString());
|
||||
RootedString replacement(cx, args[2].toString());
|
||||
JSString* result = str_replaceAll_string_raw(cx, string, pattern, replacement);
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setString(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::intrinsic_StringSplitString(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
|
|
@ -2401,7 +2420,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
JS_INLINABLE_FN("IsTypedArray",
|
||||
intrinsic_IsInstanceOfBuiltin<TypedArrayObject>, 1,0,
|
||||
IntrinsicIsTypedArray),
|
||||
JS_INLINABLE_FN("IsPossiblyWrappedTypedArray",intrinsic_IsPossiblyWrappedTypedArray,1,0,
|
||||
JS_INLINABLE_FN("IsPossiblyWrappedTypedArray",intrinsic_IsPossiblyWrappedBuiltin<TypedArrayObject>,1,0,
|
||||
IntrinsicIsPossiblyWrappedTypedArray),
|
||||
|
||||
JS_FN("TypedArrayBuffer", intrinsic_TypedArrayBuffer, 1,0),
|
||||
|
|
@ -2513,6 +2532,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
JS_INLINABLE_FN("IsRegExpObject",
|
||||
intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
|
||||
IsRegExpObject),
|
||||
JS_INLINABLE_FN("IsPossiblyWrappedRegExpObject", intrinsic_IsPossiblyWrappedBuiltin<RegExpObject>,1,0,
|
||||
IntrinsicIsPossiblyWrappedRegExpObject),
|
||||
JS_FN("CallRegExpMethodIfWrapped",
|
||||
CallNonGenericSelfhostedMethod<Is<RegExpObject>>, 2,0),
|
||||
JS_INLINABLE_FN("RegExpMatcher", RegExpMatcher, 4,0,
|
||||
|
|
@ -2536,6 +2557,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
JS_FN("FlatStringSearch", FlatStringSearch, 2,0),
|
||||
JS_INLINABLE_FN("StringReplaceString", intrinsic_StringReplaceString, 3, 0,
|
||||
IntrinsicStringReplaceString),
|
||||
JS_FN("StringReplaceAllString", intrinsic_StringReplaceAllString, 3, 0),
|
||||
JS_INLINABLE_FN("StringSplitString", intrinsic_StringSplitString, 2, 0,
|
||||
IntrinsicStringSplitString),
|
||||
JS_FN("StringSplitStringLimit", intrinsic_StringSplitStringLimit, 3, 0),
|
||||
|
|
|
|||
|
|
@ -1,266 +0,0 @@
|
|||
web-platform-tests
|
||||
==================
|
||||
|
||||
This directory contains the W3C
|
||||
[web-platform-tests](http://github.com/w3c/web-platform-tests). They
|
||||
can be run using `mach`:
|
||||
|
||||
mach web-platform-tests
|
||||
|
||||
To limit the testrun to certain directories use the `--include` option;
|
||||
for example:
|
||||
|
||||
mach web-platform-tests --include=dom
|
||||
|
||||
The testsuite contains a mix of javascript tests and reftests. To
|
||||
limit the type of tests that get run, use `--test-type=testharness` for
|
||||
javascript tests or `--test-type=reftest` for reftests.
|
||||
|
||||
FAQ
|
||||
---
|
||||
|
||||
* I fixed a bug and some tests have started to pass. How do I fix the
|
||||
UNEXPECTED-PASS messages when web-platform-tests is run?
|
||||
|
||||
You need to update the expectation data for those tests. See the
|
||||
section on expectations below.
|
||||
|
||||
* I want to write some new tests for the web-platform-tests
|
||||
testsuite. How do I do that?
|
||||
|
||||
See the section on tests below. You can commit the tests directly to
|
||||
the Mozilla repository under `testing/web-platform/tests` and they
|
||||
will be upstreamed next time the test is imported. For this reason
|
||||
please ensure that any tests you write are testing correct-per-spec
|
||||
behaviour even if we don't yet pass, get proper review, and have a
|
||||
commit message that makes sense outside of the Mozilla
|
||||
context. If you are writing tests that should not be upstreamed yet
|
||||
for some reason they must be located under
|
||||
`testing/web-platform/mozilla/tests`.
|
||||
|
||||
It is important to note that in order for the tests to run the
|
||||
manifest file must be updated; this should not be done by hand, but
|
||||
by running `mach wpt-manifest-update` (or `mach web-platform-tests
|
||||
--manifest-update`, if you also wish to run some tests).
|
||||
|
||||
`mach web-platform-tests-create <path>` is a helper script designed
|
||||
to help create new web-platform-tests. It opens a locally configured
|
||||
editor at `<path>` with web-platform-tests boilerplate filled in,
|
||||
and in the background runs `mach web-platform-tests
|
||||
--manifest-update <path>`, so the test being developed is added to
|
||||
the manifest and opened for interactive development.
|
||||
|
||||
* How do I write a test that requires the use of a Mozilla-specific
|
||||
feature?
|
||||
|
||||
Tests in the `mozilla/tests/` directory use the same harness but are
|
||||
not synced with any upstream. Be aware that these tests run on the
|
||||
server with a `/_mozilla/` prefix to their URLs.
|
||||
|
||||
* A test is unstable; how do I disable it?
|
||||
|
||||
See the section on disabling tests.
|
||||
|
||||
Directories
|
||||
-----------
|
||||
|
||||
`tests/` contains the tests themselves. This is a copy of a certain
|
||||
revision of web-platform-tests. Any patches modifying this directory
|
||||
will be upstreamed next time the tests are imported.
|
||||
|
||||
`harness/` contains the [wptrunner](http://github.com/w3c/wptrunner)
|
||||
test runner. Again the contents of this directory will be overwritten
|
||||
on update.
|
||||
|
||||
`meta/` contains Gecko-specific expectation data. This is explained in
|
||||
the following section.
|
||||
|
||||
`mozilla/tests` contains tests that will not be upstreamed and may
|
||||
make use of Mozilla-specific features.
|
||||
|
||||
`mozilla/meta` contains metadata for the Mozilla-specific tests.
|
||||
|
||||
Expectation Data
|
||||
----------------
|
||||
|
||||
With the tests coming from upstream, it is not guaranteed that they
|
||||
all pass in Gecko-based browsers. For this reason it is necessary to
|
||||
provide metadata about the expected results of each test. This is
|
||||
provided in a set of manifest files in the `meta/` subdirectories.
|
||||
|
||||
There is one manifest file per test with "non-default"
|
||||
expectations. By default tests are expected to PASS, and tests with
|
||||
subtests are expected to have an overall status of OK. The manifest
|
||||
file of a test has the same path as the test file but under the `meta`
|
||||
directory rather than the `tests` directory and has the suffix `.ini`.
|
||||
|
||||
The format of these files is similar to `ini` files, but with a couple
|
||||
of important differences; sections can be nested using indentation,
|
||||
and only `:` is permitted as a key-value separator. For example the
|
||||
expectation file for a test with one failing subtest and one erroring
|
||||
subtest might look like:
|
||||
|
||||
[filename.html]
|
||||
type: testharness
|
||||
|
||||
[Subtest name for failing test]
|
||||
expected: FAIL
|
||||
|
||||
[Subtest name for erroring test]
|
||||
expected: ERROR
|
||||
|
||||
Expectations can also be made platform-specific using a simple
|
||||
python-like conditional syntax e.g. for a test that times out on linux
|
||||
but otherwise fails:
|
||||
|
||||
[filename.html]
|
||||
type: reftest
|
||||
expected:
|
||||
if os == "linux": TIMEOUT
|
||||
FAIL
|
||||
|
||||
The available variables for the conditions are those provided by
|
||||
[mozinfo](http://mozbase.readthedocs.org/en/latest/mozinfo.html).
|
||||
|
||||
For more information on manifest files, see the
|
||||
[wptrunner documentation](http://wptrunner.readthedocs.org/en/latest/expectation.html).
|
||||
|
||||
Autogenerating Expectation Data
|
||||
-------------------------------
|
||||
|
||||
After changing some code it may be necessary to update the expectation
|
||||
data for the relevant tests. This can of course be done manually, but
|
||||
tools are available to automate much of the process.
|
||||
|
||||
First one must run the tests that have changed status, and save the
|
||||
raw log output to a file:
|
||||
|
||||
mach web-platform-tests --include=url/of/test.html --log-raw=new_results.log
|
||||
|
||||
Then the `web-platform-tests-update` command may be run using this log
|
||||
data to update the expectation files:
|
||||
|
||||
mach web-platform-tests-update --no-check-clean new_results.log
|
||||
|
||||
By default this only updates the results data for the current
|
||||
platform. To forcibly overwrite all existing result data, use the
|
||||
`--ignore-existing` option to the update command.
|
||||
|
||||
Disabling Tests
|
||||
---------------
|
||||
|
||||
Tests are disabled using the same manifest files used to set
|
||||
expectation values. For example, if a test is unstable on Windows, it
|
||||
can be disabled using an ini file with the contents:
|
||||
|
||||
[filename.html]
|
||||
type: testharness
|
||||
disabled:
|
||||
if os == "win": https://bugzilla.mozilla.org/show_bug.cgi?id=1234567
|
||||
|
||||
Enabling Prefs
|
||||
--------------
|
||||
|
||||
Some tests require specific prefs to be enabled before running. These
|
||||
prefs can be set in the expectation data using a `prefs` key with a
|
||||
comma-seperate list of `pref.name:value` items to set e.g.
|
||||
|
||||
[filename.html]
|
||||
prefs: [dom.serviceWorkers.enabled:true,
|
||||
dom.serviceWorkers.exemptFromPerDomainMax:true,
|
||||
dom.caches.enabled:true]
|
||||
|
||||
Setting per-Directory Metadata
|
||||
------------------------------
|
||||
|
||||
Occasionally it is useful to set metadata for an entire directory of
|
||||
tests e.g. to disable then all, or to enable prefs for every test. In
|
||||
that case it is possible to create a `__dir__.ini` file in the
|
||||
metadata directory corresponding to the tests for which you want to
|
||||
set this metadata e.g. to disable all the tests in
|
||||
`tests/feature/unsupported/`, one might create
|
||||
`meta/feature/unsupported/__dir__.ini` with the contents:
|
||||
|
||||
disabled: Feature is unsupported
|
||||
|
||||
Settings set in this way are inherited into subdirectories. It is
|
||||
possible to unset a value that has been set in a parent using the
|
||||
special token `@Reset` (usually used with prefs), or to force a value
|
||||
to true or false using `@True` and `@False`. For example to enable
|
||||
the tests in `meta/feature/unsupported/subfeature-supported` one might
|
||||
create an ini file
|
||||
`meta/feature/unsupported/subfeature-supported/__dir__.ini` like:
|
||||
|
||||
disabled: @False
|
||||
|
||||
Test Format
|
||||
-----------
|
||||
|
||||
Javascript tests are written using
|
||||
[testharness.js](http://github.com/w3c/testharness.js/). Reftests are
|
||||
similar to standard Gecko reftests without an explicit manifest file,
|
||||
but with in-test or filename conventions for identifying the
|
||||
reference.
|
||||
|
||||
Full documentation on test authoring and submission can be found on
|
||||
[testthewebforward.org](http://testthewebforward.org/docs).
|
||||
|
||||
Test Manifest
|
||||
-------------
|
||||
|
||||
web-platform-tests use a large auto-generated JSON file as their
|
||||
manifest. This stores data about the type of tests, their references,
|
||||
if any, and their timeout, gathered by inspecting the filenames and
|
||||
the contents of the test files.
|
||||
|
||||
In order to update the manifest it is recommended that you run `mach
|
||||
web-platform-tests --manifest-update`. This rescans the test directory
|
||||
looking for new, removed, or altered tests.
|
||||
|
||||
Running Tests In Other Browsers
|
||||
-------------------------------
|
||||
|
||||
web-platform-tests is cross browser, and the runner is compatible with
|
||||
multiple browsers. Therefore it's possible to check the behaviour of
|
||||
tests in other browsers. This is somewhat more involved than running
|
||||
them in Firefox since extra dependencies may be required. For example
|
||||
to test in Chrome:
|
||||
|
||||
1. Download the chromedriver binary and place it somewhere sensible
|
||||
e.g. `~/bin`
|
||||
|
||||
2. In your gecko source tree activate the virtualenv created by mach,
|
||||
since this has most dependencies already installed. This is typically
|
||||
in objdir/_virtualenv and is activated via e.g.
|
||||
|
||||
source objdir/_virtualenv/bin/activate
|
||||
|
||||
3. Install the extra requirements:
|
||||
|
||||
cd testing/web-platform/harness
|
||||
pip install -r requirements_chrome.txt
|
||||
|
||||
4. Edit the config file `testing/web-platform/wptrunner.ini` so that
|
||||
Chrome support is enabled by changing the section that reads:
|
||||
|
||||
[products]
|
||||
firefox =
|
||||
|
||||
to read
|
||||
|
||||
[products]
|
||||
firefox =
|
||||
chrome =
|
||||
|
||||
(alternatively create a new config file elsewhere and use the
|
||||
`--config` option to `runtests.py` to point wptrunner at this config
|
||||
file).
|
||||
|
||||
5. Run `runtests.py` using the location of chromedriver as
|
||||
the binary:
|
||||
|
||||
cd testing/web-platform
|
||||
python runtests.py --product=chrome --binary=~/bin/chromedriver --log-mach=-
|
||||
|
||||
By default this will use the same test checkout and metadata as are in
|
||||
the Gecko tree, so it's easy to compare behaviour relative to Firefox.
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 1 (0x1)
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: CN=web-platform-tests
|
||||
Validity
|
||||
Not Before: Dec 22 12:09:15 2014 GMT
|
||||
Not After : Dec 21 12:09:15 2024 GMT
|
||||
Subject: CN=web-platform-tests
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:c0:3e:c3:bd:b7:9e:2e:3e:ec:1f:a5:ae:9f:85:
|
||||
8d:c0:59:a4:40:a8:fe:66:cf:ef:7d:1f:4a:91:52:
|
||||
e6:55:0f:e5:69:b8:44:da:62:17:c7:88:28:ad:ec:
|
||||
6d:b0:00:cd:a1:69:0e:e8:19:84:58:ad:e9:ce:31:
|
||||
50:6c:a4:82:14:91:08:5a:a3:ae:c8:49:13:19:18:
|
||||
7b:5b:2b:44:30:eb:bf:c7:7c:bb:d4:32:17:6a:4d:
|
||||
eb:84:f1:65:9b:9d:21:3e:91:ae:74:75:b3:7c:b4:
|
||||
cd:fa:98:73:d4:7c:d1:5d:1b:89:72:f7:d4:52:bd:
|
||||
05:a3:c1:cf:b6:58:e7:51:ec:c2:71:c7:f6:0b:00:
|
||||
97:58:f9:cb:c3:18:46:7b:55:0f:90:bf:da:a3:7d:
|
||||
d6:c5:48:ea:b3:b5:a8:12:96:ac:38:65:10:b9:b1:
|
||||
69:cb:4e:3b:4c:c3:83:74:33:63:b4:cc:fe:65:c1:
|
||||
ad:12:51:f1:02:72:50:49:03:ab:a6:28:20:41:15:
|
||||
ca:77:15:d9:85:55:69:9d:31:c1:db:12:be:46:db:
|
||||
e6:d3:8f:2f:66:2d:9b:61:08:30:57:6c:d9:4f:b1:
|
||||
6a:a8:e5:90:e3:e2:68:96:45:6e:3f:de:5e:13:fe:
|
||||
8a:bd:f1:3f:7f:26:ec:3c:1a:80:b0:a8:ec:52:c5:
|
||||
11:03
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Basic Constraints:
|
||||
CA:TRUE
|
||||
X509v3 Subject Key Identifier:
|
||||
6A:AB:53:64:92:36:87:23:34:B3:1D:6F:85:4B:F5:DF:5A:5C:74:8F
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:6A:AB:53:64:92:36:87:23:34:B3:1D:6F:85:4B:F5:DF:5A:5C:74:8F
|
||||
DirName:/CN=web-platform-tests
|
||||
serial:01
|
||||
|
||||
X509v3 Key Usage:
|
||||
Certificate Sign
|
||||
X509v3 Extended Key Usage:
|
||||
TLS Web Server Authentication
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
46:af:02:04:97:2f:5b:00:11:c5:8f:c4:e1:2b:23:62:d1:61:
|
||||
4a:28:ed:82:82:57:d7:28:65:88:a5:61:16:20:37:02:90:16:
|
||||
0b:05:43:46:db:bd:3d:b4:4d:1c:6e:85:ff:5d:dc:0f:a4:a4:
|
||||
29:98:24:ae:39:ab:e4:97:a9:10:bc:a5:b9:4b:c1:2e:5a:ce:
|
||||
89:32:00:f1:fc:94:35:a6:71:c8:9c:d9:05:43:44:6c:68:62:
|
||||
ae:b1:71:20:17:5f:c4:fb:ae:05:e6:26:e5:41:88:cc:db:51:
|
||||
55:ed:85:a0:c9:e5:68:65:a7:fa:7a:db:8f:81:61:60:50:0b:
|
||||
16:b0:10:49:19:bb:70:0e:37:09:03:20:e8:a2:b9:e5:eb:c2:
|
||||
6a:7b:4f:60:cd:fb:22:0b:27:c6:0d:2d:e2:32:cc:28:de:c6:
|
||||
e2:14:6a:ad:3f:c4:6e:78:9d:71:24:9b:56:c4:54:28:e9:ec:
|
||||
09:6e:34:cc:6d:5c:bc:e6:68:96:44:ff:62:d0:54:a0:a4:37:
|
||||
d8:f7:9f:bc:bb:dc:ad:2c:49:e2:64:b9:4d:aa:e4:22:e6:df:
|
||||
3a:17:23:13:c1:aa:0e:94:27:46:5d:11:b9:0b:dc:3d:cf:93:
|
||||
20:eb:18:56:c5:ac:e3:92:eb:55:d3:cb:ce:e4:9c:21:85:d6:
|
||||
21:93:92:4f
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDTzCCAjegAwIBAgIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJ3ZWIt
|
||||
cGxhdGZvcm0tdGVzdHMwHhcNMTQxMjIyMTIwOTE1WhcNMjQxMjIxMTIwOTE1WjAd
|
||||
MRswGQYDVQQDDBJ3ZWItcGxhdGZvcm0tdGVzdHMwggEiMA0GCSqGSIb3DQEBAQUA
|
||||
A4IBDwAwggEKAoIBAQDAPsO9t54uPuwfpa6fhY3AWaRAqP5mz+99H0qRUuZVD+Vp
|
||||
uETaYhfHiCit7G2wAM2haQ7oGYRYrenOMVBspIIUkQhao67ISRMZGHtbK0Qw67/H
|
||||
fLvUMhdqTeuE8WWbnSE+ka50dbN8tM36mHPUfNFdG4ly99RSvQWjwc+2WOdR7MJx
|
||||
x/YLAJdY+cvDGEZ7VQ+Qv9qjfdbFSOqztagSlqw4ZRC5sWnLTjtMw4N0M2O0zP5l
|
||||
wa0SUfECclBJA6umKCBBFcp3FdmFVWmdMcHbEr5G2+bTjy9mLZthCDBXbNlPsWqo
|
||||
5ZDj4miWRW4/3l4T/oq98T9/Juw8GoCwqOxSxREDAgMBAAGjgZkwgZYwDAYDVR0T
|
||||
BAUwAwEB/zAdBgNVHQ4EFgQUaqtTZJI2hyM0sx1vhUv131pcdI8wRQYDVR0jBD4w
|
||||
PIAUaqtTZJI2hyM0sx1vhUv131pcdI+hIaQfMB0xGzAZBgNVBAMMEndlYi1wbGF0
|
||||
Zm9ybS10ZXN0c4IBATALBgNVHQ8EBAMCAgQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
|
||||
DQYJKoZIhvcNAQELBQADggEBAEavAgSXL1sAEcWPxOErI2LRYUoo7YKCV9coZYil
|
||||
YRYgNwKQFgsFQ0bbvT20TRxuhf9d3A+kpCmYJK45q+SXqRC8pblLwS5azokyAPH8
|
||||
lDWmccic2QVDRGxoYq6xcSAXX8T7rgXmJuVBiMzbUVXthaDJ5Whlp/p624+BYWBQ
|
||||
CxawEEkZu3AONwkDIOiiueXrwmp7T2DN+yILJ8YNLeIyzCjexuIUaq0/xG54nXEk
|
||||
m1bEVCjp7AluNMxtXLzmaJZE/2LQVKCkN9j3n7y73K0sSeJkuU2q5CLm3zoXIxPB
|
||||
qg6UJ0ZdEbkL3D3PkyDrGFbFrOOS61XTy87knCGF1iGTkk8=
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIb9ES7h6YGBcCAggA
|
||||
MBQGCCqGSIb3DQMHBAi2ZVLgq1XvagSCBMjAmGEVvjwXvF00veuQmJsmcjVHB/qP
|
||||
VqSXjCQ94orZb89UfFnPO9zXdvLWxrwb5WP6bbQv+Sh4htExXCD5XZi1AzWNCybe
|
||||
da0vvQGgjdzUh2fCrG4K7J0w20lrYgw3HVSj/WtmdbdZFdoX+BgXrxcqkE3M5opZ
|
||||
3UD3yIQeXSxUkh3iv6zzZaWujxjDI2JpwxRmMVbrr8OeBrKJsqB2DnKmq+emmvEF
|
||||
iXTN3Ww/Aj6GIqfPZ8jpVdwcVN5QpeHAh7b2lszt7GEOGcBhutPq4Aqy8PIiDR80
|
||||
sUYI7V8OXm+Y45DnfkvsogZEifOiUrQ2U+aGDu+Zt88661wVzjq+voJlz8EaIPCE
|
||||
B/NS2SgNqI2/DrjEEecn6hjgHWIUBwOfeNoSi1Tri6KZFyxG26LE/V8Cd50yodx9
|
||||
pBgFxdCbmYLeRcVeXW2bu0ZMjPddRlR5MHfrkM5ZAze7nRxoiyWnB/U8pPf+bQvx
|
||||
K4P9KcwCOeHigkaCYZKq7nmZyEy4km89zIugT/YWhMWyVwylTpagaiiJwYLjug8n
|
||||
CbFZWAkORBIl2g/YCuTBUJtC2IWX8kw+nYVwqBszpZyC6nbha2UmhQDfMAowQA5v
|
||||
n1LnV8I6f7u6HidbB8WX2UZoh03A4beCBz+dq2VaUquLTL4KQTIz+6rw7nEysrnH
|
||||
TIb8SlwsYAlzzwyyM9dSWt7iQeNjmH7zL0MozMs3LKHIrsWi7ZZh8BUYnT2vKdNV
|
||||
2ZLOMcR0tYVmVZ8uYkR9kny/fbZcKN54xScohA2UX261W+sWiEgN+RaBsQ79pFgi
|
||||
vYldfjaGNSvftXa590xn2tlS6/suB5MxiW5g3PuBg5XtVZ95l0f1n376Xh41sJv8
|
||||
YHrCtFHOlSpDJULGiXVh/wXBmS7qJ8DhnUUG699EdlsFf6Qg22WB3AZRvEJdYC4z
|
||||
P8W+jZ15NTDbHg3Hv7/CFYVzbXv2w0jkiqQgDF/wc6t/EdLD+2hzcN+nJGjtxZbn
|
||||
xjbXcg98CUMU+dc/aD4N45K9e9rPg3+iZLwvsRvwx+MszmgxxPv05pNyRO7RVk8r
|
||||
gkyyp9/CJFme+4nFKUc0dUy2yNXZtklTX0XKm/YNKin6uUMlIArIa54Cfvt9QslV
|
||||
iD+SxU1ZHmzwKT82+5ZeIRLNWvFV/9E4nD+BTagK2Fdwnsu1S2k7ItD9lK/cBPGS
|
||||
0tz1HWv4Auj3wMPZklp3SQluOl6eAIVqqI9GaX/d42DctBQWLTa27YibWyNIcw7o
|
||||
3N8GDREMawTBdDRwlZ3oT+yiGLX1c8ds2o0/4IcJlOkDoxXErmdlZo9oVe6z4R7g
|
||||
62yR53atVTLoUnAjxHXx0bJiyayv9Y3wjOEvuhuqdd9F+HOhTtAHr/BJQNhEk+z8
|
||||
531CZTJjb1p11PbOtHGV2IeB0S82mxkkXRykEXOb89ZpDHNRiMinThRkoCmuRI9r
|
||||
dTiES9B02yMPxJ3sLQyDxCoS5mwfcAqKTeK+yCvTvBy+t5tw63DbWlMp/7Ahy65K
|
||||
rWMHdwqwfoB+ZYw5sYZdPvuBVAT01I2JbOqX36RacQultFns2OinxOJHa1HjtXyS
|
||||
cPVEkMa7ci3Ym9j5RQNLVsgJe7YK9HixX5HjQFAowAH2pXZ5pKJIJYxPIUKtZlsz
|
||||
qbM=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzhNaLAVkYhdHc
|
||||
Mt8495CFGz6lXoE+L/w6X3937yO7OognD74lRs1jfcuV2KVQENKi0reX0Q1s+/kF
|
||||
6G+oS72VZ557lFipbZP94BLFzbSKZFIxXw7jiYRx2pjdS+wCJaV9Nf5j2rOs7KVG
|
||||
Dw1kI1xt8+zMKGMjwEua7I/B7rGiPnJNcLUJweu0EFU8i+oblH5LdOb0n0+mRTC1
|
||||
8Li00VlQZQqGU+pMn570WGwx9Rc6b1eLy1/wKAtFko0wIEn/UuYsyxia1+buPk80
|
||||
NRUTxQLaxV+++1vOjb+1NXY8fOacOyaHTY2A5hbGJ/JQSbZydENJSUQ4u3hDI+4W
|
||||
Ptli5qXXAgMBAAECggEBAIcwDQSnIjo2ZECHytQykpG6X6XXEksLhc1Lp0lhPC49
|
||||
uNR5pX6a4AcBb3PLr0opMQZO2tUoKA0ff3t0e8loKD+/xXhY0Z/dlioEOP7elwv0
|
||||
2nS1mhe9spCuxpk4GGXRhdtR8t2tj8s0do3YvgPgITXoEDX6YBZHNGhZpzSrFPgQ
|
||||
/c3eGCVmzWYuLFfdj5OPQ9bwTaY4JSvDLZT0/WTgiica7VySwfz3HP1fFqNykTiK
|
||||
ACQREvtxfk5Ym2nT6oni7CM2zOEJL9SXicXI5HO4bERH0ZYh//F3g6mwGiFXUJPd
|
||||
NKgaTM1oT9kRGkUaEYsRWrddwR8d5mXLvBuTJbgIsSECgYEA1+2uJSYRW1OqbhYP
|
||||
ms59YQHSs3VjpJpnCV2zNa2Wixs57KS2cOH7B6KrQCogJFLtgCDVLtyoErfVkD7E
|
||||
FivTgYr1pVCRppJddQzXik31uOINOBVffr7/09g3GcRN+ubHPZPq3K+dD6gHa3Aj
|
||||
0nH1EjEEV0QpSTQFn87OF2mc9wcCgYEA1NVqMbbzd+9Xft5FXuSbX6E+S02dOGat
|
||||
SgpnkTM80rjqa6eHdQzqk3JqyteHPgdi1vdYRlSPOj/X+6tySY0Ej9sRnYOfddA2
|
||||
kpiDiVkmiqVolyJPY69Utj+E3TzJ1vhCQuYknJmB7zP9tDcTxMeq0l/NaWvGshEK
|
||||
yC4UTQog1rECgYASOFILfGzWgfbNlzr12xqlRtwanHst9oFfPvLSQrWDQ2bd2wAy
|
||||
Aj+GY2mD3oobxouX1i1m6OOdwLlalJFDNauBMNKNgoDnx03vhIfjebSURy7KXrNS
|
||||
JJe9rm7n07KoyzRgs8yLlp3wJkOKA0pihY8iW9R78JpzPNqEo5SsURMXnQKBgBlV
|
||||
gfuC9H4tPjP6zzUZbyk1701VYsaI6k2q6WMOP0ox+q1v1p7nN7DvaKjWeOG4TVqb
|
||||
PKW6gQYE/XeWk9cPcyCQigs+1KdYbnaKsvWRaBYO1GFREzQhdarv6qfPCZOOH40J
|
||||
Cgid+Sp4/NULzU2aGspJ3xCSZKdjge4MFhyJfRkxAoGBAJlwqY4nue0MBLGNpqcs
|
||||
WwDtSasHvegKAcxGBKL5oWPbLBk7hk+hdqc8f6YqCkCNqv/ooBspL15ESItL+6yT
|
||||
zt0YkK4oH9tmLDb+rvqZ7ZdXbWSwKITMoCyyHUtT6OKt/RtA0Vdy9LPnP27oSO/C
|
||||
dk8Qf7KgKZLWo0ZNkvw38tEC
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 2 (0x2)
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: CN=web-platform-tests
|
||||
Validity
|
||||
Not Before: Dec 22 12:09:16 2014 GMT
|
||||
Not After : Dec 21 12:09:16 2024 GMT
|
||||
Subject: CN=web-platform.test
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:b3:84:d6:8b:01:59:18:85:d1:dc:32:df:38:f7:
|
||||
90:85:1b:3e:a5:5e:81:3e:2f:fc:3a:5f:7f:77:ef:
|
||||
23:bb:3a:88:27:0f:be:25:46:cd:63:7d:cb:95:d8:
|
||||
a5:50:10:d2:a2:d2:b7:97:d1:0d:6c:fb:f9:05:e8:
|
||||
6f:a8:4b:bd:95:67:9e:7b:94:58:a9:6d:93:fd:e0:
|
||||
12:c5:cd:b4:8a:64:52:31:5f:0e:e3:89:84:71:da:
|
||||
98:dd:4b:ec:02:25:a5:7d:35:fe:63:da:b3:ac:ec:
|
||||
a5:46:0f:0d:64:23:5c:6d:f3:ec:cc:28:63:23:c0:
|
||||
4b:9a:ec:8f:c1:ee:b1:a2:3e:72:4d:70:b5:09:c1:
|
||||
eb:b4:10:55:3c:8b:ea:1b:94:7e:4b:74:e6:f4:9f:
|
||||
4f:a6:45:30:b5:f0:b8:b4:d1:59:50:65:0a:86:53:
|
||||
ea:4c:9f:9e:f4:58:6c:31:f5:17:3a:6f:57:8b:cb:
|
||||
5f:f0:28:0b:45:92:8d:30:20:49:ff:52:e6:2c:cb:
|
||||
18:9a:d7:e6:ee:3e:4f:34:35:15:13:c5:02:da:c5:
|
||||
5f:be:fb:5b:ce:8d:bf:b5:35:76:3c:7c:e6:9c:3b:
|
||||
26:87:4d:8d:80:e6:16:c6:27:f2:50:49:b6:72:74:
|
||||
43:49:49:44:38:bb:78:43:23:ee:16:3e:d9:62:e6:
|
||||
a5:d7
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Basic Constraints:
|
||||
CA:FALSE
|
||||
X509v3 Subject Key Identifier:
|
||||
2D:98:A3:99:39:1C:FE:E9:9A:6D:17:94:D2:3A:96:EE:C8:9E:04:22
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:6A:AB:53:64:92:36:87:23:34:B3:1D:6F:85:4B:F5:DF:5A:5C:74:8F
|
||||
|
||||
X509v3 Key Usage:
|
||||
Digital Signature, Non Repudiation, Key Encipherment
|
||||
X509v3 Extended Key Usage:
|
||||
TLS Web Server Authentication
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:web-platform.test, DNS:www.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--lve-6lad.web-platform.test, DNS:www2.web-platform.test, DNS:www1.web-platform.test
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
33:db:f7:f0:f6:92:16:4f:2d:42:bc:b8:aa:e6:ab:5e:f9:b9:
|
||||
b0:48:ae:b5:8d:cc:02:7b:e9:6f:4e:75:f7:17:a0:5e:7b:87:
|
||||
06:49:48:83:c5:bb:ca:95:07:37:0e:5d:e3:97:de:9e:0c:a4:
|
||||
82:30:11:81:49:5d:50:29:72:92:a5:ca:17:b1:7c:f1:32:11:
|
||||
17:57:e6:59:c1:ac:e3:3b:26:d2:94:97:50:6a:b9:54:88:84:
|
||||
9b:6f:b1:06:f5:80:04:22:10:14:b1:f5:97:25:fc:66:d6:69:
|
||||
a3:36:08:85:23:ff:8e:3c:2b:e0:6d:e7:61:f1:00:8f:61:3d:
|
||||
b0:87:ad:72:21:f6:f0:cc:4f:c9:20:bf:83:11:0f:21:f4:b8:
|
||||
c0:dd:9c:51:d7:bb:27:32:ec:ab:a4:62:14:28:32:da:f2:87:
|
||||
80:68:9c:ea:ac:eb:f5:7f:f5:de:f4:c0:39:91:c8:76:a4:ee:
|
||||
d0:a8:50:db:c1:4b:f9:c4:3d:d9:e8:8e:b6:3f:c0:96:79:12:
|
||||
d8:fa:4d:0a:b3:36:76:aa:4e:b2:82:2f:a2:d4:0d:db:fd:64:
|
||||
77:6f:6e:e9:94:7f:0f:c8:3a:3c:96:3d:cd:4d:6c:ba:66:95:
|
||||
f7:b4:9d:a4:94:9f:97:b3:9a:0d:dc:18:8c:11:0b:56:65:8e:
|
||||
46:4c:e6:5e
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID2jCCAsKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJ3ZWIt
|
||||
cGxhdGZvcm0tdGVzdHMwHhcNMTQxMjIyMTIwOTE2WhcNMjQxMjIxMTIwOTE2WjAc
|
||||
MRowGAYDVQQDExF3ZWItcGxhdGZvcm0udGVzdDCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBALOE1osBWRiF0dwy3zj3kIUbPqVegT4v/Dpff3fvI7s6iCcP
|
||||
viVGzWN9y5XYpVAQ0qLSt5fRDWz7+QXob6hLvZVnnnuUWKltk/3gEsXNtIpkUjFf
|
||||
DuOJhHHamN1L7AIlpX01/mPas6zspUYPDWQjXG3z7MwoYyPAS5rsj8HusaI+ck1w
|
||||
tQnB67QQVTyL6huUfkt05vSfT6ZFMLXwuLTRWVBlCoZT6kyfnvRYbDH1FzpvV4vL
|
||||
X/AoC0WSjTAgSf9S5izLGJrX5u4+TzQ1FRPFAtrFX777W86Nv7U1djx85pw7JodN
|
||||
jYDmFsYn8lBJtnJ0Q0lJRDi7eEMj7hY+2WLmpdcCAwEAAaOCASQwggEgMAkGA1Ud
|
||||
EwQCMAAwHQYDVR0OBBYEFC2Yo5k5HP7pmm0XlNI6lu7IngQiMB8GA1UdIwQYMBaA
|
||||
FGqrU2SSNocjNLMdb4VL9d9aXHSPMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggr
|
||||
BgEFBQcDATCBsAYDVR0RBIGoMIGlghF3ZWItcGxhdGZvcm0udGVzdIIVd3d3Lndl
|
||||
Yi1wbGF0Zm9ybS50ZXN0gil4bi0tbjhqNmRzNTNsd3drcnFodjI4YS53ZWItcGxh
|
||||
dGZvcm0udGVzdIIeeG4tLWx2ZS02bGFkLndlYi1wbGF0Zm9ybS50ZXN0ghZ3d3cy
|
||||
LndlYi1wbGF0Zm9ybS50ZXN0ghZ3d3cxLndlYi1wbGF0Zm9ybS50ZXN0MA0GCSqG
|
||||
SIb3DQEBCwUAA4IBAQAz2/fw9pIWTy1CvLiq5qte+bmwSK61jcwCe+lvTnX3F6Be
|
||||
e4cGSUiDxbvKlQc3Dl3jl96eDKSCMBGBSV1QKXKSpcoXsXzxMhEXV+ZZwazjOybS
|
||||
lJdQarlUiISbb7EG9YAEIhAUsfWXJfxm1mmjNgiFI/+OPCvgbedh8QCPYT2wh61y
|
||||
IfbwzE/JIL+DEQ8h9LjA3ZxR17snMuyrpGIUKDLa8oeAaJzqrOv1f/Xe9MA5kch2
|
||||
pO7QqFDbwUv5xD3Z6I62P8CWeRLY+k0KszZ2qk6ygi+i1A3b/WR3b27plH8PyDo8
|
||||
lj3NTWy6ZpX3tJ2klJ+Xs5oN3BiMEQtWZY5GTOZe
|
||||
-----END CERTIFICATE-----
|
||||
7
testing/web-platform/harness/.gitignore
vendored
7
testing/web-platform/harness/.gitignore
vendored
|
|
@ -1,7 +0,0 @@
|
|||
*.py[co]
|
||||
*~
|
||||
*#
|
||||
\#*
|
||||
_virtualenv
|
||||
test/test.cfg
|
||||
test/metadata/MANIFEST.json
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
exclude MANIFEST.in
|
||||
include requirements.txt
|
||||
include wptrunner/browsers/b2g_setup/*
|
||||
include wptrunner.default.ini
|
||||
include wptrunner/testharness_runner.html
|
||||
include wptrunner/testharnessreport.js
|
||||
include wptrunner/testharnessreport-servo.js
|
||||
include wptrunner/executors/testharness_marionette.js
|
||||
include wptrunner/executors/testharness_webdriver.js
|
||||
include wptrunner/executors/reftest.js
|
||||
include wptrunner/executors/reftest-wait.js
|
||||
include wptrunner/config.json
|
||||
include wptrunner/browsers/server-locations.txt
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
wptrunner: A web-platform-tests harness
|
||||
=======================================
|
||||
|
||||
wptrunner is a harness for running the W3C `web-platform-tests testsuite`_.
|
||||
|
||||
.. contents::
|
||||
|
||||
Installation
|
||||
~~~~~~~~~~~~
|
||||
|
||||
wptrunner is expected to be installed into a virtualenv using pip. For
|
||||
development, it can be installed using the `-e` option::
|
||||
|
||||
pip install -e ./
|
||||
|
||||
Running the Tests
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
After installation, the command ``wptrunner`` should be available to run
|
||||
the tests.
|
||||
|
||||
The ``wptrunner`` command takes multiple options, of which the
|
||||
following are most significant:
|
||||
|
||||
``--product`` (defaults to `firefox`)
|
||||
The product to test against: `b2g`, `chrome`, `firefox`, or `servo`.
|
||||
|
||||
``--binary`` (required if product is `firefox` or `servo`)
|
||||
The path to a binary file for the product (browser) to test against.
|
||||
|
||||
``--webdriver-binary`` (required if product is `chrome`)
|
||||
The path to a `driver` binary; e.g., a `chromedriver` binary.
|
||||
|
||||
``--certutil-binary`` (required if product is `firefox` [#]_)
|
||||
The path to a `certutil` binary (for tests that must be run over https).
|
||||
|
||||
``--metadata`` (required)
|
||||
The path to a directory containing test metadata. [#]_
|
||||
|
||||
``--tests`` (required)
|
||||
The path to a directory containing a web-platform-tests checkout.
|
||||
|
||||
``--prefs-root`` (required only when testing a Firefox binary)
|
||||
The path to a directory containing Firefox test-harness preferences. [#]_
|
||||
|
||||
``--config`` (should default to `wptrunner.default.ini`)
|
||||
The path to the config (ini) file.
|
||||
|
||||
.. [#] The ``--certutil-binary`` option is required when the product is
|
||||
``firefox`` unless ``--ssl-type=none`` is specified.
|
||||
|
||||
.. [#] The ``--metadata`` path is to a directory that contains:
|
||||
|
||||
* a ``MANIFEST.json`` file (instructions on generating this file are
|
||||
available in the `detailed documentation
|
||||
<http://wptrunner.readthedocs.org/en/latest/usage.html#installing-wptrunner>`_);
|
||||
and
|
||||
* (optionally) any expectation files (see below)
|
||||
|
||||
.. [#] Example ``--prefs-root`` value: ``~/mozilla-central/testing/profiles``.
|
||||
|
||||
There are also a variety of other options available; use ``--help`` to
|
||||
list them.
|
||||
|
||||
-------------------------------
|
||||
Example: How to start wptrunner
|
||||
-------------------------------
|
||||
|
||||
To test a Firefox Nightly build in an OS X environment, you might start
|
||||
wptrunner using something similar to the following example::
|
||||
|
||||
wptrunner --metadata=~/web-platform-tests/ --tests=~/web-platform-tests/ \
|
||||
--binary=~/mozilla-central/obj-x86_64-apple-darwin14.3.0/dist/Nightly.app/Contents/MacOS/firefox \
|
||||
--certutil-binary=~/mozilla-central/obj-x86_64-apple-darwin14.3.0/security/nss/cmd/certutil/certutil \
|
||||
--prefs-root=~/mozilla-central/testing/profiles
|
||||
|
||||
And to test a Chromium build in an OS X environment, you might start
|
||||
wptrunner using something similar to the following example::
|
||||
|
||||
wptrunner --metadata=~/web-platform-tests/ --tests=~/web-platform-tests/ \
|
||||
--binary=~/chromium/src/out/Release/Chromium.app/Contents/MacOS/Chromium \
|
||||
--webdriver-binary=/usr/local/bin/chromedriver --product=chrome
|
||||
|
||||
-------------------------------------
|
||||
Example: How to run a subset of tests
|
||||
-------------------------------------
|
||||
|
||||
To restrict a test run just to tests in a particular web-platform-tests
|
||||
subdirectory, specify the directory name in the positional arguments after
|
||||
the options; for example, run just the tests in the `dom` subdirectory::
|
||||
|
||||
wptrunner --metadata=~/web-platform-tests/ --tests=~/web-platform-tests/ \
|
||||
--binary=/path/to/firefox --certutil-binary=/path/to/certutil \
|
||||
--prefs-root=/path/to/testing/profiles \
|
||||
dom
|
||||
|
||||
Output
|
||||
~~~~~~
|
||||
|
||||
By default wptrunner just dumps its entire output as raw JSON messages
|
||||
to stdout. This is convenient for piping into other tools, but not ideal
|
||||
for humans reading the output.
|
||||
|
||||
As an alternative, you can use the ``--log-mach`` option, which provides
|
||||
output in a reasonable format for humans. The option requires a value:
|
||||
either the path for a file to write the `mach`-formatted output to, or
|
||||
"`-`" (a hyphen) to write the `mach`-formatted output to stdout.
|
||||
|
||||
When using ``--log-mach``, output of the full raw JSON log is still
|
||||
available, from the ``--log-raw`` option. So to output the full raw JSON
|
||||
log to a file and a human-readable summary to stdout, you might start
|
||||
wptrunner using something similar to the following example::
|
||||
|
||||
wptrunner --metadata=~/web-platform-tests/ --tests=~/web-platform-tests/ \
|
||||
--binary=/path/to/firefox --certutil-binary=/path/to/certutil \
|
||||
--prefs-root=/path/to/testing/profiles \
|
||||
--log-raw=output.log --log-mach=-
|
||||
|
||||
Expectation Data
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
wptrunner is designed to be used in an environment where it is not
|
||||
just necessary to know which tests passed, but to compare the results
|
||||
between runs. For this reason it is possible to store the results of a
|
||||
previous run in a set of ini-like "expectation files". This format is
|
||||
documented below. To generate the expectation files use `wptrunner` with
|
||||
the `--log-raw=/path/to/log/file` option. This can then be used as
|
||||
input to the `wptupdate` tool.
|
||||
|
||||
Expectation File Format
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Metadata about tests, notably including their expected results, is
|
||||
stored in a modified ini-like format that is designed to be human
|
||||
editable, but also to be machine updatable.
|
||||
|
||||
Each test file that requires metadata to be specified (because it has
|
||||
a non-default expectation or because it is disabled, for example) has
|
||||
a corresponding expectation file in the `metadata` directory. For
|
||||
example a test file `html/test1.html` containing a failing test would
|
||||
have an expectation file called `html/test1.html.ini` in the
|
||||
`metadata` directory.
|
||||
|
||||
An example of an expectation file is::
|
||||
|
||||
example_default_key: example_value
|
||||
|
||||
[filename.html]
|
||||
type: testharness
|
||||
|
||||
[subtest1]
|
||||
expected: FAIL
|
||||
|
||||
[subtest2]
|
||||
expected:
|
||||
if platform == 'win': TIMEOUT
|
||||
if platform == 'osx': ERROR
|
||||
FAIL
|
||||
|
||||
[filename.html?query=something]
|
||||
type: testharness
|
||||
disabled: bug12345
|
||||
|
||||
The file consists of two elements, key-value pairs and
|
||||
sections.
|
||||
|
||||
Sections are delimited by headings enclosed in square brackets. Any
|
||||
closing square bracket in the heading itself my be escaped with a
|
||||
backslash. Each section may then contain any number of key-value pairs
|
||||
followed by any number of subsections. So that it is clear which data
|
||||
belongs to each section without the use of end-section markers, the
|
||||
data for each section (i.e. the key-value pairs and subsections) must
|
||||
be indented using spaces. Indentation need only be consistent, but
|
||||
using two spaces per level is recommended.
|
||||
|
||||
In a test expectation file, each resource provided by the file has a
|
||||
single section, with the section heading being the part after the last
|
||||
`/` in the test url. Tests that have subsections may have subsections
|
||||
for those subtests in which the heading is the name of the subtest.
|
||||
|
||||
Simple key-value pairs are of the form::
|
||||
|
||||
key: value
|
||||
|
||||
Note that unlike ini files, only `:` is a valid seperator; `=` will
|
||||
not work as expected. Key-value pairs may also have conditional
|
||||
values of the form::
|
||||
|
||||
key:
|
||||
if condition1: value1
|
||||
if condition2: value2
|
||||
default
|
||||
|
||||
In this case each conditional is evaluated in turn and the value is
|
||||
that on the right hand side of the first matching conditional. In the
|
||||
case that no condition matches, the unconditional default is used. If
|
||||
no condition matches and no default is provided it is equivalent to
|
||||
the key not being present. Conditionals use a simple python-like expression
|
||||
language e.g.::
|
||||
|
||||
if debug and (platform == "linux" or platform == "osx"): FAIL
|
||||
|
||||
For test expectations the avaliable variables are those in the
|
||||
`run_info` which for desktop are `version`, `os`, `bits`, `processor`,
|
||||
`debug` and `product`.
|
||||
|
||||
Key-value pairs specified at the top level of the file before any
|
||||
sections are special as they provide defaults for the rest of the file
|
||||
e.g.::
|
||||
|
||||
key1: value1
|
||||
|
||||
[section 1]
|
||||
key2: value2
|
||||
|
||||
[section 2]
|
||||
key1: value3
|
||||
|
||||
In this case, inside section 1, `key1` would have the value `value1`
|
||||
and `key2` the value `value2` whereas in section 2 `key1` would have
|
||||
the value `value3` and `key2` would be undefined.
|
||||
|
||||
The web-platform-test harness knows about several keys:
|
||||
|
||||
`expected`
|
||||
Must evaluate to a possible test status indicating the expected
|
||||
result of the test. The implicit default is PASS or OK when the
|
||||
field isn't present.
|
||||
|
||||
`disabled`
|
||||
Any value indicates that the test is disabled.
|
||||
|
||||
`type`
|
||||
The test type e.g. `testharness`, `reftest`, or `wdspec`.
|
||||
|
||||
`reftype`
|
||||
The type of comparison for reftests; either `==` or `!=`.
|
||||
|
||||
`refurl`
|
||||
The reference url for reftests.
|
||||
|
||||
.. _`web-platform-tests testsuite`: https://github.com/w3c/web-platform-tests
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/wptrunner.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/wptrunner.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/wptrunner"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/wptrunner"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 19 KiB |
|
|
@ -1,267 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# wptrunner documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon May 19 18:14:20 2014.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'wptrunner'
|
||||
copyright = u''
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.3'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.3'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'wptrunnerdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'wptrunner.tex', u'wptrunner Documentation',
|
||||
u'James Graham', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'wptrunner', u'wptrunner Documentation',
|
||||
[u'James Graham'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'wptrunner', u'wptrunner Documentation',
|
||||
u'James Graham', 'wptrunner', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'python': ('http://docs.python.org/', None),
|
||||
'mozlog': ('http://mozbase.readthedocs.org/en/latest/', None)}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
wptrunner Design
|
||||
================
|
||||
|
||||
The design of wptrunner is intended to meet the following
|
||||
requirements:
|
||||
|
||||
* Possible to run tests from W3C web-platform-tests.
|
||||
|
||||
* Tests should be run as fast as possible. In particular it should
|
||||
not be necessary to restart the browser between tests, or similar.
|
||||
|
||||
* As far as possible, the tests should run in a "normal" browser and
|
||||
browsing context. In particular many tests assume that they are
|
||||
running in a top-level browsing context, so we must avoid the use
|
||||
of an ``iframe`` test container.
|
||||
|
||||
* It must be possible to deal with all kinds of behaviour of the
|
||||
browser runder test, for example, crashing, hanging, etc.
|
||||
|
||||
* It should be possible to add support for new platforms and browsers
|
||||
with minimal code changes.
|
||||
|
||||
* It must be possible to run tests in parallel to further improve
|
||||
performance.
|
||||
|
||||
* Test output must be in a machine readable form.
|
||||
|
||||
Architecture
|
||||
------------
|
||||
|
||||
In order to meet the above requirements, wptrunner is designed to
|
||||
push as much of the test scheduling as possible into the harness. This
|
||||
allows the harness to monitor the state of the browser and perform
|
||||
appropriate action if it gets into an unwanted state e.g. kill the
|
||||
browser if it appears to be hung.
|
||||
|
||||
The harness will typically communicate with the browser via some remote
|
||||
control protocol such as WebDriver. However for browsers where no such
|
||||
protocol is supported, other implementation strategies are possible,
|
||||
typically at the expense of speed.
|
||||
|
||||
The overall architecture of wptrunner is shown in the diagram below:
|
||||
|
||||
.. image:: architecture.svg
|
||||
|
||||
The main entry point to the code is :py:func:`run_tests` in
|
||||
``wptrunner.py``. This is responsible for setting up the test
|
||||
environment, loading the list of tests to be executed, and invoking
|
||||
the remainder of the code to actually execute some tests.
|
||||
|
||||
The test environment is encapsulated in the
|
||||
:py:class:`TestEnvironment` class. This defers to code in
|
||||
``web-platform-tests`` which actually starts the required servers to
|
||||
run the tests.
|
||||
|
||||
The set of tests to run is defined by the
|
||||
:py:class:`TestLoader`. This is constructed with a
|
||||
:py:class:`TestFilter` (not shown), which takes any filter arguments
|
||||
from the command line to restrict the set of tests that will be
|
||||
run. The :py:class:`TestLoader` reads both the ``web-platform-tests``
|
||||
JSON manifest and the expectation data stored in ini files and
|
||||
produces a :py:class:`multiprocessing.Queue` of tests to run, and
|
||||
their expected results.
|
||||
|
||||
Actually running the tests happens through the
|
||||
:py:class:`ManagerGroup` object. This takes the :py:class:`Queue` of
|
||||
tests to be run and starts a :py:class:`testrunner.TestRunnerManager` for each
|
||||
instance of the browser under test that will be started. These
|
||||
:py:class:`TestRunnerManager` instances are each started in their own
|
||||
thread.
|
||||
|
||||
A :py:class:`TestRunnerManager` coordinates starting the product under
|
||||
test, and outputting results from the test. In the case that the test
|
||||
has timed out or the browser has crashed, it has to restart the
|
||||
browser to ensure the test run can continue. The functionality for
|
||||
initialising the browser under test, and probing its state
|
||||
(e.g. whether the process is still alive) is implemented through a
|
||||
:py:class:`Browser` object. An implementation of this class must be
|
||||
provided for each product that is supported.
|
||||
|
||||
The functionality for actually running the tests is provided by a
|
||||
:py:class:`TestRunner` object. :py:class:`TestRunner` instances are
|
||||
run in their own child process created with the
|
||||
:py:mod:`multiprocessing` module. This allows them to run concurrently
|
||||
and to be killed and restarted as required. Communication between the
|
||||
:py:class:`TestRunnerManager` and the :py:class:`TestRunner` is
|
||||
provided by a pair of queues, one for sending messages in each
|
||||
direction. In particular test results are sent from the
|
||||
:py:class:`TestRunner` to the :py:class:`TestRunnerManager` using one
|
||||
of these queues.
|
||||
|
||||
The :py:class:`TestRunner` object is generic in that the same
|
||||
:py:class:`TestRunner` is used regardless of the product under
|
||||
test. However the details of how to run the test may vary greatly with
|
||||
the product since different products support different remote control
|
||||
protocols (or none at all). These protocol-specific parts are placed
|
||||
in the :py:class:`Executor` object. There is typically a different
|
||||
:py:class:`Executor` class for each combination of control protocol
|
||||
and test type. The :py:class:`TestRunner` is responsible for pulling
|
||||
each test off the :py:class:`Queue` of tests and passing it down to
|
||||
the :py:class:`Executor`.
|
||||
|
||||
The executor often requires access to details of the particular
|
||||
browser instance that it is testing so that it knows e.g. which port
|
||||
to connect to to send commands to the browser. These details are
|
||||
encapsulated in the :py:class:`ExecutorBrowser` class.
|
||||
|
|
@ -1,248 +0,0 @@
|
|||
Expectation Data
|
||||
================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
For use in continuous integration systems, and other scenarios where
|
||||
regression tracking is required, wptrunner supports storing and
|
||||
loading the expected result of each test in a test run. Typically
|
||||
these expected results will initially be generated by running the
|
||||
testsuite in a baseline build. They may then be edited by humans as
|
||||
new features are added to the product that change the expected
|
||||
results. The expected results may also vary for a single product
|
||||
depending on the platform on which it is run. Therefore, the raw
|
||||
structured log data is not a suitable format for storing these
|
||||
files. Instead something is required that is:
|
||||
|
||||
* Human readable
|
||||
|
||||
* Human editable
|
||||
|
||||
* Machine readable / writable
|
||||
|
||||
* Capable of storing test id / result pairs
|
||||
|
||||
* Suitable for storing in a version control system (i.e. text-based)
|
||||
|
||||
The need for different results per platform means either having
|
||||
multiple expectation files for each platform, or having a way to
|
||||
express conditional values within a certain file. The former would be
|
||||
rather cumbersome for humans updating the expectation files, so the
|
||||
latter approach has been adopted, leading to the requirement:
|
||||
|
||||
* Capable of storing result values that are conditional on the platform.
|
||||
|
||||
There are few extant formats that meet these requirements, so
|
||||
wptrunner uses a bespoke ``expectation manifest`` format, which is
|
||||
closely based on the standard ``ini`` format.
|
||||
|
||||
Directory Layout
|
||||
----------------
|
||||
|
||||
Expectation manifest files must be stored under the ``metadata``
|
||||
directory passed to the test runner. The directory layout follows that
|
||||
of web-platform-tests with each test path having a corresponding
|
||||
manifest file. Tests that differ only by query string, or reftests
|
||||
with the same test path but different ref paths share the same
|
||||
reference file. The file name is taken from the last /-separated part
|
||||
of the path, suffixed with ``.ini``.
|
||||
|
||||
As an optimisation, files which produce only default results
|
||||
(i.e. ``PASS`` or ``OK``) don't require a corresponding manifest file.
|
||||
|
||||
For example a test with url::
|
||||
|
||||
/spec/section/file.html?query=param
|
||||
|
||||
would have an expectation file ::
|
||||
|
||||
metadata/spec/section/file.html.ini
|
||||
|
||||
|
||||
.. _wptupdate-label:
|
||||
|
||||
Generating Expectation Files
|
||||
----------------------------
|
||||
|
||||
wptrunner provides the tool ``wptupdate`` to generate expectation
|
||||
files from the results of a set of baseline test runs. The basic
|
||||
syntax for this is::
|
||||
|
||||
wptupdate [options] [logfile]...
|
||||
|
||||
Each ``logfile`` is a structured log file from a previous run. These
|
||||
can be generated from wptrunner using the ``--log-raw`` option
|
||||
e.g. ``--log-raw=structured.log``. The default behaviour is to update
|
||||
all the test data for the particular combination of hardware and OS
|
||||
used in the run corresponding to the log data, whilst leaving any
|
||||
other expectations untouched.
|
||||
|
||||
wptupdate takes several useful options:
|
||||
|
||||
``--sync``
|
||||
Pull the latest version of web-platform-tests from the
|
||||
upstream specified in the config file. If this is specified in
|
||||
combination with logfiles, it is assumed that the results in the log
|
||||
files apply to the post-update tests.
|
||||
|
||||
``--no-check-clean``
|
||||
Don't attempt to check if the working directory is clean before
|
||||
doing the update (assuming that the working directory is a git or
|
||||
mercurial tree).
|
||||
|
||||
``--patch``
|
||||
Create a a git commit, or a mq patch, with the changes made by wptupdate.
|
||||
|
||||
``--ignore-existing``
|
||||
Overwrite all the expectation data for any tests that have a result
|
||||
in the passed log files, not just data for the same platform.
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
Update the local copy of web-platform-tests without changing the
|
||||
expectation data and commit (or create a mq patch for) the result::
|
||||
|
||||
wptupdate --patch --sync
|
||||
|
||||
Update all the expectations from a set of cross-platform test runs::
|
||||
|
||||
wptupdate --no-check-clean --patch osx.log linux.log windows.log
|
||||
|
||||
Add expectation data for some new tests that are expected to be
|
||||
platform-independent::
|
||||
|
||||
wptupdate --no-check-clean --patch --ignore-existing tests.log
|
||||
|
||||
Manifest Format
|
||||
---------------
|
||||
The format of the manifest files is based on the ini format. Files are
|
||||
divided into sections, each (apart from the root section) having a
|
||||
heading enclosed in square braces. Within each section are key-value
|
||||
pairs. There are several notable differences from standard .ini files,
|
||||
however:
|
||||
|
||||
* Sections may be hierarchically nested, with significant whitespace
|
||||
indicating nesting depth.
|
||||
|
||||
* Only ``:`` is valid as a key/value separator
|
||||
|
||||
A simple example of a manifest file is::
|
||||
|
||||
root_key: root_value
|
||||
|
||||
[section]
|
||||
section_key: section_value
|
||||
|
||||
[subsection]
|
||||
subsection_key: subsection_value
|
||||
|
||||
[another_section]
|
||||
another_key: another_value
|
||||
|
||||
Conditional Values
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In order to support values that depend on some external data, the
|
||||
right hand side of a key/value pair can take a set of conditionals
|
||||
rather than a plain value. These values are placed on a new line
|
||||
following the key, with significant indentation. Conditional values
|
||||
are prefixed with ``if`` and terminated with a colon, for example::
|
||||
|
||||
key:
|
||||
if cond1: value1
|
||||
if cond2: value2
|
||||
value3
|
||||
|
||||
In this example, the value associated with ``key`` is determined by
|
||||
first evaluating ``cond1`` against external data. If that is true,
|
||||
``key`` is assigned the value ``value1``, otherwise ``cond2`` is
|
||||
evaluated in the same way. If both ``cond1`` and ``cond2`` are false,
|
||||
the unconditional ``value3`` is used.
|
||||
|
||||
Conditions themselves use a Python-like expression syntax. Operands
|
||||
can either be variables, corresponding to data passed in, numbers
|
||||
(integer or floating point; exponential notation is not supported) or
|
||||
quote-delimited strings. Equality is tested using ``==`` and
|
||||
inequality by ``!=``. The operators ``and``, ``or`` and ``not`` are
|
||||
used in the expected way. Parentheses can also be used for
|
||||
grouping. For example::
|
||||
|
||||
key:
|
||||
if (a == 2 or a == 3) and b == "abc": value1
|
||||
if a == 1 or b != "abc": value2
|
||||
value3
|
||||
|
||||
Here ``a`` and ``b`` are variables, the value of which will be
|
||||
supplied when the manifest is used.
|
||||
|
||||
Expectation Manifests
|
||||
---------------------
|
||||
|
||||
When used for expectation data, manifests have the following format:
|
||||
|
||||
* A section per test URL described by the manifest, with the section
|
||||
heading being the part of the test URL following the last ``/`` in
|
||||
the path (this allows multiple tests in a single manifest file with
|
||||
the same path part of the URL, but different query parts).
|
||||
|
||||
* A subsection per subtest, with the heading being the title of the
|
||||
subtest.
|
||||
|
||||
* A key ``type`` indicating the test type. This takes the values
|
||||
``testharness`` and ``reftest``.
|
||||
|
||||
* For reftests, keys ``reftype`` indicating the reference type
|
||||
(``==`` or ``!=``) and ``refurl`` indicating the URL of the
|
||||
reference.
|
||||
|
||||
* A key ``expected`` giving the expectation value of each (sub)test.
|
||||
|
||||
* A key ``disabled`` which can be set to any value to indicate that
|
||||
the (sub)test is disabled and should either not be run (for tests)
|
||||
or that its results should be ignored (subtests).
|
||||
|
||||
* A key ``restart-after`` which can be set to any value to indicate that
|
||||
the runner should restart the browser after running this test (e.g. to
|
||||
clear out unwanted state).
|
||||
|
||||
* Variables ``debug``, ``os``, ``version``, ``processor`` and
|
||||
``bits`` that describe the configuration of the browser under
|
||||
test. ``debug`` is a boolean indicating whether a build is a debug
|
||||
build. ``os`` is a string indicating the operating system, and
|
||||
``version`` a string indicating the particular version of that
|
||||
operating system. ``processor`` is a string indicating the
|
||||
processor architecture and ``bits`` an integer indicating the
|
||||
number of bits. This information is typically provided by
|
||||
:py:mod:`mozinfo`.
|
||||
|
||||
* Top level keys are taken as defaults for the whole file. So, for
|
||||
example, a top level key with ``expected: FAIL`` would indicate
|
||||
that all tests and subtests in the file are expected to fail,
|
||||
unless they have an ``expected`` key of their own.
|
||||
|
||||
An simple example manifest might look like::
|
||||
|
||||
[test.html?variant=basic]
|
||||
type: testharness
|
||||
|
||||
[Test something unsupported]
|
||||
expected: FAIL
|
||||
|
||||
[test.html?variant=broken]
|
||||
expected: ERROR
|
||||
|
||||
[test.html?variant=unstable]
|
||||
disabled: http://test.bugs.example.org/bugs/12345
|
||||
|
||||
A more complex manifest with conditional properties might be::
|
||||
|
||||
[canvas_test.html]
|
||||
expected:
|
||||
if os == "osx": FAIL
|
||||
if os == "windows" and version == "XP": FAIL
|
||||
PASS
|
||||
|
||||
Note that ``PASS`` in the above works, but is unnecessary; ``PASS``
|
||||
(or ``OK``) is always the default expectation for (sub)tests.
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
.. wptrunner documentation master file, created by
|
||||
sphinx-quickstart on Mon May 19 18:14:20 2014.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to wptrunner's documentation!
|
||||
=====================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
usage
|
||||
expectation
|
||||
design
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\wptrunner.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\wptrunner.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
Getting Started
|
||||
===============
|
||||
|
||||
Installing wptrunner
|
||||
--------------------
|
||||
|
||||
The easiest way to install wptrunner is into a virtualenv, using pip::
|
||||
|
||||
virtualenv wptrunner
|
||||
cd wptrunner
|
||||
source bin/activate
|
||||
pip install wptrunner
|
||||
|
||||
This will install the base dependencies for wptrunner, but not any
|
||||
extra dependencies required to test against specific browsers. In
|
||||
order to do this you must use use the extra requirements files in
|
||||
``$VIRTUAL_ENV/requirements/requirements_browser.txt``. For example,
|
||||
in order to test against Firefox you would have to run::
|
||||
|
||||
pip install -r requirements/requirements_firefox.txt
|
||||
|
||||
If you intend to work on the code, the ``-e`` option to pip should be
|
||||
used in combination with a source checkout i.e. inside a virtual
|
||||
environment created as above::
|
||||
|
||||
git clone https://github.com/w3c/wptrunner.git
|
||||
cd wptrunner
|
||||
pip install -e ./
|
||||
|
||||
In addition to the dependencies installed by pip, wptrunner requires
|
||||
a copy of the web-platform-tests repository. This can be located
|
||||
anywhere on the filesystem, but the easiest option is to put it
|
||||
under the same parent directory as the wptrunner checkout::
|
||||
|
||||
git clone https://github.com/w3c/web-platform-tests.git
|
||||
|
||||
It is also necessary to generate a web-platform-tests ``MANIFEST.json``
|
||||
file. It's recommended to also put that under the same parent directory as
|
||||
the wptrunner checkout, in a directory named ``meta``::
|
||||
|
||||
mkdir meta
|
||||
cd web-platform-tests
|
||||
python manifest --path ../meta/MANIFEST.json
|
||||
|
||||
The ``MANIFEST.json`` file needs to be regenerated each time the
|
||||
web-platform-tests checkout is updated. To aid with the update process
|
||||
there is a tool called ``wptupdate``, which is described in
|
||||
:ref:`wptupdate-label`.
|
||||
|
||||
Running the Tests
|
||||
-----------------
|
||||
|
||||
A test run is started using the ``wptrunner`` command. The command
|
||||
takes multiple options, of which the following are most significant:
|
||||
|
||||
``--product`` (defaults to `firefox`)
|
||||
The product to test against: `b2g`, `chrome`, `firefox`, or `servo`.
|
||||
|
||||
``--binary`` (required if product is `firefox` or `servo`)
|
||||
The path to a binary file for the product (browser) to test against.
|
||||
|
||||
``--webdriver-binary`` (required if product is `chrome`)
|
||||
The path to a `*driver` binary; e.g., a `chromedriver` binary.
|
||||
|
||||
``--certutil-binary`` (required if product is `firefox` [#]_)
|
||||
The path to a `certutil` binary (for tests that must be run over https).
|
||||
|
||||
``--metadata`` (required only when not `using default paths`_)
|
||||
The path to a directory containing test metadata. [#]_
|
||||
|
||||
``--tests`` (required only when not `using default paths`_)
|
||||
The path to a directory containing a web-platform-tests checkout.
|
||||
|
||||
``--prefs-root`` (required only when testing a Firefox binary)
|
||||
The path to a directory containing Firefox test-harness preferences. [#]_
|
||||
|
||||
``--config`` (should default to `wptrunner.default.ini`)
|
||||
The path to the config (ini) file.
|
||||
|
||||
.. [#] The ``--certutil-binary`` option is required when the product is
|
||||
``firefox`` unless ``--ssl-type=none`` is specified.
|
||||
|
||||
.. [#] The ``--metadata`` path is to a directory that contains:
|
||||
|
||||
* a ``MANIFEST.json`` file (the web-platform-tests documentation has
|
||||
instructions on generating this file)
|
||||
* (optionally) any expectation files (see :ref:`wptupdate-label`)
|
||||
|
||||
.. [#] Example ``--prefs-root`` value: ``~/mozilla-central/testing/profiles``.
|
||||
|
||||
There are also a variety of other command-line options available; use
|
||||
``--help`` to list them.
|
||||
|
||||
The following examples show how to start wptrunner with various options.
|
||||
|
||||
------------------
|
||||
Starting wptrunner
|
||||
------------------
|
||||
|
||||
The examples below assume the following directory layout,
|
||||
though no specific folder structure is required::
|
||||
|
||||
~/testtwf/wptrunner # wptrunner checkout
|
||||
~/testtwf/web-platform-tests # web-platform-tests checkout
|
||||
~/testtwf/meta # metadata
|
||||
|
||||
To test a Firefox Nightly build in an OS X environment, you might start
|
||||
wptrunner using something similar to the following example::
|
||||
|
||||
wptrunner --metadata=~/testtwf/meta/ --tests=~/testtwf/web-platform-tests/ \
|
||||
--binary=~/mozilla-central/obj-x86_64-apple-darwin14.3.0/dist/Nightly.app/Contents/MacOS/firefox \
|
||||
--certutil-binary=~/mozilla-central/obj-x86_64-apple-darwin14.3.0/security/nss/cmd/certutil/certutil \
|
||||
--prefs-root=~/mozilla-central/testing/profiles
|
||||
|
||||
|
||||
And to test a Chromium build in an OS X environment, you might start
|
||||
wptrunner using something similar to the following example::
|
||||
|
||||
wptrunner --metadata=~/testtwf/meta/ --tests=~/testtwf/web-platform-tests/ \
|
||||
--binary=~/chromium/src/out/Release/Chromium.app/Contents/MacOS/Chromium \
|
||||
--webdriver-binary=/usr/local/bin/chromedriver --product=chrome
|
||||
|
||||
--------------------
|
||||
Running test subsets
|
||||
--------------------
|
||||
|
||||
To restrict a test run just to tests in a particular web-platform-tests
|
||||
subdirectory, specify the directory name in the positional arguments after
|
||||
the options; for example, run just the tests in the `dom` subdirectory::
|
||||
|
||||
wptrunner --metadata=~/testtwf/meta --tests=~/testtwf/web-platform-tests/ \
|
||||
--binary=/path/to/firefox --certutil-binary=/path/to/certutil \
|
||||
--prefs-root=/path/to/testing/profiles \
|
||||
dom
|
||||
|
||||
-------------------
|
||||
Running in parallel
|
||||
-------------------
|
||||
|
||||
To speed up the testing process, use the ``--processes`` option to have
|
||||
wptrunner run multiple browser instances in parallel. For example, to
|
||||
have wptrunner attempt to run tests against with six browser instances
|
||||
in parallel, specify ``--processes=6``. But note that behaviour in this
|
||||
mode is necessarily less deterministic than with ``--processes=1`` (the
|
||||
default), so there may be more noise in the test results.
|
||||
|
||||
-------------------
|
||||
Using default paths
|
||||
-------------------
|
||||
|
||||
The (otherwise-required) ``--tests`` and ``--metadata`` command-line
|
||||
options/flags be omitted if any configuration file is found that
|
||||
contains a section specifying the ``tests`` and ``metadata`` keys.
|
||||
|
||||
See the `Configuration File`_ section for more information about
|
||||
configuration files, including information about their expected
|
||||
locations.
|
||||
|
||||
The content of the ``wptrunner.default.ini`` default configuration file
|
||||
makes wptrunner look for tests (that is, a web-platform-tests checkout)
|
||||
as a subdirectory of the current directory named ``tests``, and for
|
||||
metadata files in a subdirectory of the current directory named ``meta``.
|
||||
|
||||
Output
|
||||
------
|
||||
|
||||
wptrunner uses the :py:mod:`mozlog` package for output. This
|
||||
structures events such as test results or log messages as JSON objects
|
||||
that can then be fed to other tools for interpretation. More details
|
||||
about the message format are given in the
|
||||
:py:mod:`mozlog` documentation.
|
||||
|
||||
By default the raw JSON messages are dumped to stdout. This is
|
||||
convenient for piping into other tools, but not ideal for humans
|
||||
reading the output. :py:mod:`mozlog` comes with several other
|
||||
formatters, which are accessible through command line options. The
|
||||
general format of these options is ``--log-name=dest``, where ``name``
|
||||
is the name of the format and ``dest`` is a path to a destination
|
||||
file, or ``-`` for stdout. The raw JSON data is written by the ``raw``
|
||||
formatter so, the default setup corresponds to ``--log-raw=-``.
|
||||
|
||||
A reasonable output format for humans is provided as ``mach``. So in
|
||||
order to output the full raw log to a file and a human-readable
|
||||
summary to stdout, one might pass the options::
|
||||
|
||||
--log-raw=output.log --log-mach=-
|
||||
|
||||
Configuration File
|
||||
------------------
|
||||
|
||||
wptrunner uses a ``.ini`` file to control some configuration
|
||||
sections. The file has three sections; ``[products]``,
|
||||
``[manifest:default]`` and ``[web-platform-tests]``.
|
||||
|
||||
``[products]`` is used to
|
||||
define the set of available products. By default this section is empty
|
||||
which means that all the products distributed with wptrunner are
|
||||
enabled (although their dependencies may not be installed). The set
|
||||
of enabled products can be set by using the product name as the
|
||||
key. For built in products the value is empty. It is also possible to
|
||||
provide the path to a script implementing the browser functionality
|
||||
e.g.::
|
||||
|
||||
[products]
|
||||
chrome =
|
||||
netscape4 = path/to/netscape.py
|
||||
|
||||
``[manifest:default]`` specifies the default paths for the tests and metadata,
|
||||
relative to the config file. For example::
|
||||
|
||||
[manifest:default]
|
||||
tests = ~/testtwf/web-platform-tests
|
||||
metadata = ~/testtwf/meta
|
||||
|
||||
|
||||
``[web-platform-tests]`` is used to set the properties of the upstream
|
||||
repository when updating the paths. ``remote_url`` specifies the git
|
||||
url to pull from; ``branch`` the branch to sync against and
|
||||
``sync_path`` the local path, relative to the configuration file, to
|
||||
use when checking out the tests e.g.::
|
||||
|
||||
[web-platform-tests]
|
||||
remote_url = https://github.com/w3c/web-platform-tests.git
|
||||
branch = master
|
||||
sync_path = sync
|
||||
|
||||
A configuration file must contain all the above fields; falling back
|
||||
to the default values for unspecified fields is not yet supported.
|
||||
|
||||
The ``wptrunner`` and ``wptupdate`` commands will use configuration
|
||||
files in the following order:
|
||||
|
||||
* Any path supplied with a ``--config`` flag to the command.
|
||||
|
||||
* A file called ``wptrunner.ini`` in the current directory
|
||||
|
||||
* The default configuration file (``wptrunner.default.ini`` in the
|
||||
source directory)
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
html5lib >= 0.99
|
||||
mozinfo >= 0.7
|
||||
mozlog >= 3.0
|
||||
mozdebug >= 0.1
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
fxos_appgen >= 0.5
|
||||
mozdevice >= 0.41
|
||||
gaiatest >= 0.26
|
||||
marionette_client >= 0.7.10
|
||||
moznetwork >= 0.24
|
||||
mozprofile >= 0.21
|
||||
mozrunner >= 6.1
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
mozprocess >= 0.19
|
||||
selenium >= 2.41.0
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
marionette_driver >= 0.4
|
||||
mozprofile >= 0.21
|
||||
mozprocess >= 0.19
|
||||
mozcrash >= 0.13
|
||||
mozrunner >= 6.7
|
||||
|
|
@ -1 +0,0 @@
|
|||
mozprocess >= 0.19
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
here = os.path.split(__file__)[0]
|
||||
|
||||
PACKAGE_NAME = 'wptrunner'
|
||||
PACKAGE_VERSION = '1.14'
|
||||
|
||||
# Dependencies
|
||||
with open(os.path.join(here, "requirements.txt")) as f:
|
||||
deps = f.read().splitlines()
|
||||
|
||||
# Browser-specific requirements
|
||||
requirements_files = glob.glob(os.path.join(here, "requirements_*.txt"))
|
||||
|
||||
profile_dest = None
|
||||
dest_exists = False
|
||||
|
||||
setup(name=PACKAGE_NAME,
|
||||
version=PACKAGE_VERSION,
|
||||
description="Harness for running the W3C web-platform-tests against various products",
|
||||
author='Mozilla Automation and Testing Team',
|
||||
author_email='tools@lists.mozilla.org',
|
||||
license='MPL 2.0',
|
||||
packages=find_packages(exclude=["tests", "metadata", "prefs"]),
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'wptrunner = wptrunner.wptrunner:main',
|
||||
'wptupdate = wptrunner.update:main',
|
||||
]
|
||||
},
|
||||
zip_safe=False,
|
||||
platforms=['Any'],
|
||||
classifiers=['Development Status :: 4 - Beta',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)',
|
||||
'Operating System :: OS Independent'],
|
||||
package_data={"wptrunner": ["executors/testharness_marionette.js",
|
||||
"executors/testharness_webdriver.js",
|
||||
"executors/reftest.js",
|
||||
"executors/reftest-wait.js",
|
||||
"testharnessreport.js",
|
||||
"testharness_runner.html",
|
||||
"config.json",
|
||||
"wptrunner.default.ini",
|
||||
"browsers/server-locations.txt",
|
||||
"browsers/b2g_setup/*",
|
||||
"prefs/*"]},
|
||||
include_package_data=True,
|
||||
data_files=[("requirements", requirements_files)],
|
||||
install_requires=deps
|
||||
)
|
||||
|
||||
if "install" in sys.argv:
|
||||
path = os.path.relpath(os.path.join(sys.prefix, "requirements"), os.curdir)
|
||||
print textwrap.fill("""In order to use with one of the built-in browser
|
||||
products, you will need to install the extra dependencies. These are provided
|
||||
as requirements_[name].txt in the %s directory and can be installed using
|
||||
e.g.""" % path, 80)
|
||||
|
||||
print """
|
||||
|
||||
pip install -r %s/requirements_firefox.txt
|
||||
""" % path
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[reftest_and_fail.html]
|
||||
type: reftest
|
||||
expected: FAIL
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[reftest_cycle_fail.html]
|
||||
type: reftest
|
||||
expected: FAIL
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[reftest_match_fail.html]
|
||||
type: reftest
|
||||
expected: FAIL
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[reftest_mismatch_fail.html]
|
||||
type: reftest
|
||||
expected: FAIL
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[reftest_ref_timeout.html]
|
||||
type: reftest
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[reftest_timeout.html]
|
||||
type: reftest
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
prefs: ["browser.display.foreground_color:#FF0000",
|
||||
"browser.display.background_color:#000000"]
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
[test_pref_reset.html]
|
||||
prefs: [@Reset]
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[test_pref_set.html]
|
||||
prefs: ["browser.display.foreground_color:#00FF00",
|
||||
"browser.display.background_color:#000000"]
|
||||
|
|
@ -1 +0,0 @@
|
|||
disabled: true
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
[testharness_1.html]
|
||||
disabled: @False
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[testharness_0.html]
|
||||
type: testharness
|
||||
[Test that should fail]
|
||||
expected: FAIL
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[testharness_error.html]
|
||||
type: testharness
|
||||
expected: ERROR
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[testharness_timeout.html]
|
||||
type: testharness
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
[general]
|
||||
tests=/path/to/web-platform-tests/
|
||||
metadata=/path/to/web-platform-tests/
|
||||
ssl-type=none
|
||||
|
||||
# [firefox]
|
||||
# binary=/path/to/firefox
|
||||
# prefs-root=/path/to/gecko-src/testing/profiles/
|
||||
|
||||
# [servo]
|
||||
# binary=/path/to/servo-src/target/release/servo
|
||||
# exclude=testharness # Because it needs a special testharness.js
|
||||
|
||||
# [servodriver]
|
||||
# binary=/path/to/servo-src/target/release/servo
|
||||
# exclude=testharness # Because it needs a special testharness.js
|
||||
|
||||
# [chrome]
|
||||
# binary=/path/to/chrome
|
||||
# webdriver-binary=/path/to/chromedriver
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
import ConfigParser
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from StringIO import StringIO
|
||||
|
||||
from mozlog import structuredlog, reader
|
||||
from mozlog.handlers import BaseHandler, StreamHandler, StatusHandler
|
||||
from mozlog.formatters import MachFormatter
|
||||
from wptrunner import wptcommandline, wptrunner
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
def setup_wptrunner_logging(logger):
|
||||
structuredlog.set_default_logger(logger)
|
||||
wptrunner.logger = logger
|
||||
wptrunner.wptlogging.setup_stdlib_logger()
|
||||
|
||||
class ResultHandler(BaseHandler):
|
||||
def __init__(self, verbose=False, logger=None):
|
||||
self.inner = StreamHandler(sys.stdout, MachFormatter())
|
||||
BaseHandler.__init__(self, self.inner)
|
||||
self.product = None
|
||||
self.verbose = verbose
|
||||
self.logger = logger
|
||||
|
||||
self.register_message_handlers("wptrunner-test", {"set-product": self.set_product})
|
||||
|
||||
def set_product(self, product):
|
||||
self.product = product
|
||||
|
||||
def __call__(self, data):
|
||||
if self.product is not None and data["action"] in ["suite_start", "suite_end"]:
|
||||
# Hack: mozlog sets some internal state to prevent multiple suite_start or
|
||||
# suite_end messages. We actually want that here (one from the metaharness
|
||||
# and one from the individual test type harness), so override that internal
|
||||
# state (a better solution might be to not share loggers, but this works well
|
||||
# enough)
|
||||
self.logger._state.suite_started = True
|
||||
return
|
||||
|
||||
if (not self.verbose and
|
||||
(data["action"] == "process_output" or
|
||||
data["action"] == "log" and data["level"] not in ["error", "critical"])):
|
||||
return
|
||||
|
||||
if "test" in data:
|
||||
data = data.copy()
|
||||
data["test"] = "%s: %s" % (self.product, data["test"])
|
||||
|
||||
return self.inner(data)
|
||||
|
||||
def test_settings():
|
||||
return {
|
||||
"include": "_test",
|
||||
"manifest-update": "",
|
||||
"no-capture-stdio": ""
|
||||
}
|
||||
|
||||
def read_config():
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.read("test.cfg")
|
||||
|
||||
rv = {"general":{},
|
||||
"products":{}}
|
||||
|
||||
rv["general"].update(dict(parser.items("general")))
|
||||
|
||||
# This only allows one product per whatever for now
|
||||
for product in parser.sections():
|
||||
if product != "general":
|
||||
dest = rv["products"][product] = {}
|
||||
for key, value in parser.items(product):
|
||||
rv["products"][product][key] = value
|
||||
|
||||
return rv
|
||||
|
||||
def run_tests(product, kwargs):
|
||||
kwargs["test_paths"]["/_test/"] = {"tests_path": os.path.join(here, "testdata"),
|
||||
"metadata_path": os.path.join(here, "metadata")}
|
||||
|
||||
wptrunner.run_tests(**kwargs)
|
||||
|
||||
def settings_to_argv(settings):
|
||||
rv = []
|
||||
for name, value in settings.iteritems():
|
||||
key = "--%s" % name
|
||||
if not value:
|
||||
rv.append(key)
|
||||
elif isinstance(value, list):
|
||||
for item in value:
|
||||
rv.extend([key, item])
|
||||
else:
|
||||
rv.extend([key, value])
|
||||
return rv
|
||||
|
||||
def set_from_args(settings, args):
|
||||
if args.test:
|
||||
settings["include"] = args.test
|
||||
if args.tags:
|
||||
settings["tags"] = args.tags
|
||||
|
||||
def run(config, args):
|
||||
logger = structuredlog.StructuredLogger("web-platform-tests")
|
||||
logger.add_handler(ResultHandler(logger=logger, verbose=args.verbose))
|
||||
setup_wptrunner_logging(logger)
|
||||
|
||||
parser = wptcommandline.create_parser()
|
||||
|
||||
logger.suite_start(tests=[])
|
||||
|
||||
for product, product_settings in config["products"].iteritems():
|
||||
if args.product and product not in args.product:
|
||||
continue
|
||||
|
||||
settings = test_settings()
|
||||
settings.update(config["general"])
|
||||
settings.update(product_settings)
|
||||
settings["product"] = product
|
||||
set_from_args(settings, args)
|
||||
|
||||
kwargs = vars(parser.parse_args(settings_to_argv(settings)))
|
||||
wptcommandline.check_args(kwargs)
|
||||
|
||||
logger.send_message("wptrunner-test", "set-product", product)
|
||||
|
||||
run_tests(product, kwargs)
|
||||
|
||||
logger.send_message("wptrunner-test", "set-product", None)
|
||||
logger.suite_end()
|
||||
|
||||
def get_parser():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", action="store_true", default=False,
|
||||
help="verbose log output")
|
||||
parser.add_argument("--product", action="append",
|
||||
help="Specific product to include in test run")
|
||||
parser.add_argument("--pdb", action="store_true",
|
||||
help="Invoke pdb on uncaught exception")
|
||||
parser.add_argument("--tag", action="append", dest="tags",
|
||||
help="tags to select tests")
|
||||
parser.add_argument("test", nargs="*",
|
||||
help="Specific tests to include in test run")
|
||||
return parser
|
||||
|
||||
def main():
|
||||
config = read_config()
|
||||
|
||||
args = get_parser().parse_args()
|
||||
|
||||
try:
|
||||
run(config, args)
|
||||
except Exception:
|
||||
if args.pdb:
|
||||
import pdb, traceback
|
||||
print traceback.format_exc()
|
||||
pdb.post_mortem()
|
||||
else:
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<link rel=match href=green.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<style>
|
||||
:root {background-color:red}
|
||||
</style>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<link rel=match href=green.html>
|
||||
<style>
|
||||
:root {background-color:red}
|
||||
</style>
|
||||
<script>
|
||||
if (window.location.protocol === "https:") {
|
||||
document.documentElement.style.backgroundColor = "green";
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>Reftest chain that should fail</title>
|
||||
<link rel=match href=reftest_and_fail_0-ref.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>Reftest chain that should fail</title>
|
||||
<link rel=match href=red.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>Reftest with cycle, all match</title>
|
||||
<link rel=match href=reftest_cycle_0-ref.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>OR match that should pass</title>
|
||||
<link rel=match href=reftest_cycle_1-ref.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>Reftest with cycle, all match</title>
|
||||
<link rel=match href=reftest_cycle.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>Reftest with cycle, fails</title>
|
||||
<link rel=match href=reftest_cycle_fail_0-ref.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>Reftest with cycle, fails</title>
|
||||
<link rel=mismatch href=reftest_cycle_fail.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>rel=match that should pass</title>
|
||||
<link rel=match href=green.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>rel=match that should fail</title>
|
||||
<link rel=match href=red.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>rel=mismatch that should pass</title>
|
||||
<link rel=mismatch href=red.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<title>rel=mismatch that should fail</title>
|
||||
<link rel=mismatch href=green.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<title>OR match that should pass</title>
|
||||
<link rel=match href=red.html>
|
||||
<link rel=match href=green.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<html class="reftest-wait">
|
||||
<title>rel=match that should time out in the ref</title>
|
||||
<link rel=match href=reftest_ref_timeout-ref.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<html>
|
||||
<title>rel=match that should time out in the ref</title>
|
||||
<link rel=match href=reftest_ref_timeout-ref.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<html class="reftest-wait">
|
||||
<title>rel=match that should timeout</title>
|
||||
<link rel=match href=green.html>
|
||||
<style>
|
||||
:root {background-color:green}
|
||||
</style>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<title>rel=match that should fail</title>
|
||||
<link rel=match href=red.html>
|
||||
<style>
|
||||
:root {background-color:red}
|
||||
</style>
|
||||
<body class="reftest-wait">
|
||||
<script>
|
||||
setTimeout(function() {
|
||||
document.documentElement.style.backgroundColor = "green";
|
||||
body.className = "";
|
||||
}, 2000);
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>Example pref test</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<p>Test requires the pref browser.display.foreground_color to be set to #00FF00</p>
|
||||
<script>
|
||||
test(function() {
|
||||
assert_equals(getComputedStyle(document.body).color, "rgb(255, 0, 0)");
|
||||
}, "Test that pref was set");
|
||||
</script>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>Example pref test</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<p>Test requires the pref browser.display.foreground_color to be set to #00FF00</p>
|
||||
<script>
|
||||
test(function() {
|
||||
assert_equals(getComputedStyle(document.body).color, "rgb(0, 0, 0)");
|
||||
}, "Test that pref was reset");
|
||||
</script>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>Example pref test</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<p>Test requires the pref browser.display.foreground_color to be set to #FF0000</p>
|
||||
<script>
|
||||
test(function() {
|
||||
assert_equals(getComputedStyle(document.body).color, "rgb(255, 0, 0)");
|
||||
}, "Test that pref was set");
|
||||
</script>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>Example pref test</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<p>Test requires the pref browser.display.foreground_color to be set to #00FF00</p>
|
||||
<script>
|
||||
test(function() {
|
||||
assert_equals(getComputedStyle(document.body).color, "rgb(0, 255, 0)");
|
||||
}, "Test that pref was set");
|
||||
</script>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>Test should be enabled</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
test(function() {
|
||||
assert_true(true);
|
||||
}, "Test that should pass");
|
||||
</script>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>Example https test</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
test(function() {
|
||||
assert_equals(window.location.protocol, "https:");
|
||||
}, "Test that file was loaded with the correct protocol");
|
||||
|
||||
</script>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>Test should be disabled</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
test(function() {
|
||||
assert_true(false);
|
||||
}, "Test that should fail");
|
||||
</script>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>testharness.js test that should error</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
undefined_function()
|
||||
</script>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>testharness.js test with long timeout</title>
|
||||
<meta name=timeout content=long>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
var t = async_test("Long timeout test");
|
||||
setTimeout(t.step_func_done(function() {assert_true(true)}), 15*1000);
|
||||
</script>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>Simple testharness.js usage</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
|
||||
// This file should time out, obviously
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
[products]
|
||||
|
||||
[web-platform-tests]
|
||||
remote_url = https://github.com/w3c/web-platform-tests.git
|
||||
branch = master
|
||||
sync_path = %(pwd)s/sync
|
||||
|
||||
[manifest:default]
|
||||
tests = %(pwd)s/tests
|
||||
metadata = %(pwd)s/meta
|
||||
url_base = /
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
"""Subpackage where each product is defined. Each product is created by adding a
|
||||
a .py file containing a __wptrunner__ variable in the global scope. This must be
|
||||
a dictionary with the fields
|
||||
|
||||
"product": Name of the product, assumed to be unique.
|
||||
"browser": String indicating the Browser implementation used to launch that
|
||||
product.
|
||||
"executor": Dictionary with keys as supported test types and values as the name
|
||||
of the Executor implemantation that will be used to run that test
|
||||
type.
|
||||
"browser_kwargs": String naming function that takes product, binary,
|
||||
prefs_root and the wptrunner.run_tests kwargs dict as arguments
|
||||
and returns a dictionary of kwargs to use when creating the
|
||||
Browser class.
|
||||
"executor_kwargs": String naming a function that takes http server url and
|
||||
timeout multiplier and returns kwargs to use when creating
|
||||
the executor class.
|
||||
"env_options": String naming a funtion of no arguments that returns the
|
||||
arguments passed to the TestEnvironment.
|
||||
|
||||
All classes and functions named in the above dict must be imported into the
|
||||
module global scope.
|
||||
"""
|
||||
|
||||
product_list = ["b2g",
|
||||
"chrome",
|
||||
"firefox",
|
||||
"servo",
|
||||
"servodriver"]
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import fxos_appgen
|
||||
import gaiatest
|
||||
import mozdevice
|
||||
import moznetwork
|
||||
import mozrunner
|
||||
from marionette import expected
|
||||
from marionette.by import By
|
||||
from marionette.wait import Wait
|
||||
from mozprofile import FirefoxProfile, Preferences
|
||||
|
||||
from .base import get_free_port, BrowserError, Browser, ExecutorBrowser
|
||||
from ..executors.executormarionette import MarionetteTestharnessExecutor
|
||||
from ..hosts import HostsFile, HostsLine
|
||||
from ..environment import hostnames
|
||||
|
||||
here = os.path.split(__file__)[0]
|
||||
|
||||
__wptrunner__ = {"product": "b2g",
|
||||
"check_args": "check_args",
|
||||
"browser": "B2GBrowser",
|
||||
"executor": {"testharness": "B2GMarionetteTestharnessExecutor"},
|
||||
"browser_kwargs": "browser_kwargs",
|
||||
"executor_kwargs": "executor_kwargs",
|
||||
"env_options": "env_options"}
|
||||
|
||||
|
||||
def check_args(**kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def browser_kwargs(test_environment, **kwargs):
|
||||
return {"prefs_root": kwargs["prefs_root"],
|
||||
"no_backup": kwargs.get("b2g_no_backup", False)}
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
|
||||
**kwargs):
|
||||
timeout_multiplier = kwargs["timeout_multiplier"]
|
||||
if timeout_multiplier is None:
|
||||
timeout_multiplier = 2
|
||||
|
||||
executor_kwargs = {"server_config": server_config,
|
||||
"timeout_multiplier": timeout_multiplier,
|
||||
"close_after_done": False}
|
||||
|
||||
if test_type == "reftest":
|
||||
executor_kwargs["cache_manager"] = cache_manager
|
||||
|
||||
return executor_kwargs
|
||||
|
||||
|
||||
def env_options():
|
||||
return {"host": "web-platform.test",
|
||||
"bind_hostname": "false",
|
||||
"test_server_port": False}
|
||||
|
||||
|
||||
class B2GBrowser(Browser):
|
||||
used_ports = set()
|
||||
init_timeout = 180
|
||||
|
||||
def __init__(self, logger, prefs_root, no_backup=False):
|
||||
Browser.__init__(self, logger)
|
||||
logger.info("Waiting for device")
|
||||
subprocess.call(["adb", "wait-for-device"])
|
||||
self.device = mozdevice.DeviceManagerADB()
|
||||
self.marionette_port = get_free_port(2828, exclude=self.used_ports)
|
||||
self.used_ports.add(self.marionette_port)
|
||||
self.cert_test_app = None
|
||||
self.runner = None
|
||||
self.prefs_root = prefs_root
|
||||
|
||||
self.no_backup = no_backup
|
||||
self.backup_path = None
|
||||
self.backup_paths = []
|
||||
self.backup_dirs = []
|
||||
|
||||
def setup(self):
|
||||
self.logger.info("Running B2G setup")
|
||||
self.backup_path = tempfile.mkdtemp()
|
||||
|
||||
self.logger.debug("Backing up device to %s" % (self.backup_path,))
|
||||
|
||||
if not self.no_backup:
|
||||
self.backup_dirs = [("/data/local", os.path.join(self.backup_path, "local")),
|
||||
("/data/b2g/mozilla", os.path.join(self.backup_path, "profile"))]
|
||||
|
||||
self.backup_paths = [("/system/etc/hosts", os.path.join(self.backup_path, "hosts"))]
|
||||
|
||||
for remote, local in self.backup_dirs:
|
||||
self.device.getDirectory(remote, local)
|
||||
|
||||
for remote, local in self.backup_paths:
|
||||
self.device.getFile(remote, local)
|
||||
|
||||
self.setup_hosts()
|
||||
|
||||
def start(self):
|
||||
profile = FirefoxProfile()
|
||||
|
||||
profile.set_preferences({"dom.disable_open_during_load": False,
|
||||
"marionette.defaultPrefs.enabled": True})
|
||||
|
||||
self.logger.debug("Creating device runner")
|
||||
self.runner = mozrunner.B2GDeviceRunner(profile=profile)
|
||||
self.logger.debug("Starting device runner")
|
||||
self.runner.start()
|
||||
self.logger.debug("Device runner started")
|
||||
|
||||
def setup_hosts(self):
|
||||
host_ip = moznetwork.get_ip()
|
||||
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
hosts_path = os.path.join(temp_dir, "hosts")
|
||||
remote_path = "/system/etc/hosts"
|
||||
try:
|
||||
self.device.getFile("/system/etc/hosts", hosts_path)
|
||||
|
||||
with open(hosts_path) as f:
|
||||
hosts_file = HostsFile.from_file(f)
|
||||
|
||||
for canonical_hostname in hostnames:
|
||||
hosts_file.set_host(HostsLine(host_ip, canonical_hostname))
|
||||
|
||||
with open(hosts_path, "w") as f:
|
||||
hosts_file.to_file(f)
|
||||
|
||||
self.logger.info("Installing hosts file")
|
||||
|
||||
self.device.remount()
|
||||
self.device.removeFile(remote_path)
|
||||
self.device.pushFile(hosts_path, remote_path)
|
||||
finally:
|
||||
os.unlink(hosts_path)
|
||||
os.rmdir(temp_dir)
|
||||
|
||||
def load_prefs(self):
|
||||
prefs_path = os.path.join(self.prefs_root, "prefs_general.js")
|
||||
if os.path.exists(prefs_path):
|
||||
preferences = Preferences.read_prefs(prefs_path)
|
||||
else:
|
||||
self.logger.warning("Failed to find base prefs file in %s" % prefs_path)
|
||||
preferences = []
|
||||
|
||||
return preferences
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def on_output(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def cleanup(self):
|
||||
self.logger.debug("Running browser cleanup steps")
|
||||
|
||||
self.device.remount()
|
||||
|
||||
for remote, local in self.backup_dirs:
|
||||
self.device.removeDir(remote)
|
||||
self.device.pushDir(local, remote)
|
||||
|
||||
for remote, local in self.backup_paths:
|
||||
self.device.removeFile(remote)
|
||||
self.device.pushFile(local, remote)
|
||||
|
||||
shutil.rmtree(self.backup_path)
|
||||
self.device.reboot(wait=True)
|
||||
|
||||
def pid(self):
|
||||
return None
|
||||
|
||||
def is_alive(self):
|
||||
return True
|
||||
|
||||
def executor_browser(self):
|
||||
return B2GExecutorBrowser, {"marionette_port": self.marionette_port}
|
||||
|
||||
|
||||
class B2GExecutorBrowser(ExecutorBrowser):
|
||||
# The following methods are called from a different process
|
||||
def __init__(self, *args, **kwargs):
|
||||
ExecutorBrowser.__init__(self, *args, **kwargs)
|
||||
|
||||
import sys, subprocess
|
||||
|
||||
self.device = mozdevice.ADBB2G()
|
||||
self.device.forward("tcp:%s" % self.marionette_port,
|
||||
"tcp:2828")
|
||||
self.executor = None
|
||||
self.marionette = None
|
||||
self.gaia_device = None
|
||||
self.gaia_apps = None
|
||||
|
||||
def after_connect(self, executor):
|
||||
self.executor = executor
|
||||
self.marionette = executor.marionette
|
||||
self.executor.logger.debug("Running browser.after_connect steps")
|
||||
|
||||
self.gaia_apps = gaiatest.GaiaApps(marionette=executor.marionette)
|
||||
|
||||
self.executor.logger.debug("Waiting for homescreen to load")
|
||||
|
||||
# Moved out of gaia_test temporarily
|
||||
self.executor.logger.info("Waiting for B2G to be ready")
|
||||
self.wait_for_homescreen(timeout=60)
|
||||
|
||||
self.install_cert_app()
|
||||
self.use_cert_app()
|
||||
|
||||
def install_cert_app(self):
|
||||
"""Install the container app used to run the tests"""
|
||||
if fxos_appgen.is_installed("CertTest App"):
|
||||
self.executor.logger.info("CertTest App is already installed")
|
||||
return
|
||||
self.executor.logger.info("Installing CertTest App")
|
||||
app_path = os.path.join(here, "b2g_setup", "certtest_app.zip")
|
||||
fxos_appgen.install_app("CertTest App", app_path, marionette=self.marionette)
|
||||
self.executor.logger.debug("Install complete")
|
||||
|
||||
def use_cert_app(self):
|
||||
"""Start the app used to run the tests"""
|
||||
self.executor.logger.info("Homescreen loaded")
|
||||
self.gaia_apps.launch("CertTest App")
|
||||
|
||||
def wait_for_homescreen(self, timeout):
|
||||
self.executor.logger.info("Waiting for home screen to load")
|
||||
Wait(self.marionette, timeout).until(expected.element_present(
|
||||
By.CSS_SELECTOR, '#homescreen[loading-state=false]'))
|
||||
|
||||
|
||||
class B2GMarionetteTestharnessExecutor(MarionetteTestharnessExecutor):
|
||||
def after_connect(self):
|
||||
self.browser.after_connect(self)
|
||||
MarionetteTestharnessExecutor.after_connect(self)
|
||||
Binary file not shown.
|
|
@ -1,160 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from ..wptcommandline import require_arg
|
||||
|
||||
here = os.path.split(__file__)[0]
|
||||
|
||||
|
||||
def cmd_arg(name, value=None):
|
||||
prefix = "-" if platform.system() == "Windows" else "--"
|
||||
rv = prefix + name
|
||||
if value is not None:
|
||||
rv += "=" + value
|
||||
return rv
|
||||
|
||||
|
||||
def get_free_port(start_port, exclude=None):
|
||||
"""Get the first port number after start_port (inclusive) that is
|
||||
not currently bound.
|
||||
|
||||
:param start_port: Integer port number at which to start testing.
|
||||
:param exclude: Set of port numbers to skip"""
|
||||
port = start_port
|
||||
while True:
|
||||
if exclude and port in exclude:
|
||||
port += 1
|
||||
continue
|
||||
s = socket.socket()
|
||||
try:
|
||||
s.bind(("127.0.0.1", port))
|
||||
except socket.error:
|
||||
port += 1
|
||||
else:
|
||||
return port
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
def browser_command(binary, args, debug_info):
|
||||
if debug_info:
|
||||
if debug_info.requiresEscapedArgs:
|
||||
args = [item.replace("&", "\\&") for item in args]
|
||||
debug_args = [debug_info.path] + debug_info.args
|
||||
else:
|
||||
debug_args = []
|
||||
|
||||
command = [binary] + args
|
||||
|
||||
return debug_args, command
|
||||
|
||||
|
||||
class BrowserError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Browser(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
process_cls = None
|
||||
init_timeout = 30
|
||||
|
||||
def __init__(self, logger):
|
||||
"""Abstract class serving as the basis for Browser implementations.
|
||||
|
||||
The Browser is used in the TestRunnerManager to start and stop the browser
|
||||
process, and to check the state of that process. This class also acts as a
|
||||
context manager, enabling it to do browser-specific setup at the start of
|
||||
the testrun and cleanup after the run is complete.
|
||||
|
||||
:param logger: Structured logger to use for output.
|
||||
"""
|
||||
self.logger = logger
|
||||
|
||||
def __enter__(self):
|
||||
self.setup()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.cleanup()
|
||||
|
||||
def setup(self):
|
||||
"""Used for browser-specific setup that happens at the start of a test run"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def start(self):
|
||||
"""Launch the browser object and get it into a state where is is ready to run tests"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop(self):
|
||||
"""Stop the running browser process."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pid(self):
|
||||
"""pid of the browser process or None if there is no pid"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_alive(self):
|
||||
"""Boolean indicating whether the browser process is still running"""
|
||||
pass
|
||||
|
||||
def setup_ssl(self, hosts):
|
||||
"""Return a certificate to use for tests requiring ssl that will be trusted by the browser"""
|
||||
raise NotImplementedError("ssl testing not supported")
|
||||
|
||||
def cleanup(self):
|
||||
"""Browser-specific cleanup that is run after the testrun is finished"""
|
||||
pass
|
||||
|
||||
def executor_browser(self):
|
||||
"""Returns the ExecutorBrowser subclass for this Browser subclass and the keyword arguments
|
||||
with which it should be instantiated"""
|
||||
return ExecutorBrowser, {}
|
||||
|
||||
def log_crash(self, process, test):
|
||||
"""Return a list of dictionaries containing information about crashes that happend
|
||||
in the browser, or an empty list if no crashes occurred"""
|
||||
self.logger.crash(process, test)
|
||||
|
||||
|
||||
class NullBrowser(Browser):
|
||||
def start(self):
|
||||
"""No-op browser to use in scenarios where the TestRunnerManager shouldn't
|
||||
actually own the browser process (e.g. Servo where we start one browser
|
||||
per test)"""
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def pid(self):
|
||||
return None
|
||||
|
||||
def is_alive(self):
|
||||
return True
|
||||
|
||||
def on_output(self, line):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ExecutorBrowser(object):
|
||||
def __init__(self, **kwargs):
|
||||
"""View of the Browser used by the Executor object.
|
||||
This is needed because the Executor runs in a child process and
|
||||
we can't ship Browser instances between processes on Windows.
|
||||
|
||||
Typically this will have a few product-specific properties set,
|
||||
but in some cases it may have more elaborate methods for setting
|
||||
up the browser from the runner process.
|
||||
"""
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(self, k, v)
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from .base import Browser, ExecutorBrowser, require_arg
|
||||
from ..webdriver_server import ChromeDriverServer
|
||||
from ..executors import executor_kwargs as base_executor_kwargs
|
||||
from ..executors.executorselenium import (SeleniumTestharnessExecutor,
|
||||
SeleniumRefTestExecutor)
|
||||
|
||||
|
||||
__wptrunner__ = {"product": "chrome",
|
||||
"check_args": "check_args",
|
||||
"browser": "ChromeBrowser",
|
||||
"executor": {"testharness": "SeleniumTestharnessExecutor",
|
||||
"reftest": "SeleniumRefTestExecutor"},
|
||||
"browser_kwargs": "browser_kwargs",
|
||||
"executor_kwargs": "executor_kwargs",
|
||||
"env_options": "env_options"}
|
||||
|
||||
|
||||
def check_args(**kwargs):
|
||||
require_arg(kwargs, "webdriver_binary")
|
||||
|
||||
|
||||
def browser_kwargs(**kwargs):
|
||||
return {"binary": kwargs["binary"],
|
||||
"webdriver_binary": kwargs["webdriver_binary"]}
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
|
||||
**kwargs):
|
||||
from selenium.webdriver import DesiredCapabilities
|
||||
|
||||
executor_kwargs = base_executor_kwargs(test_type, server_config,
|
||||
cache_manager, **kwargs)
|
||||
executor_kwargs["close_after_done"] = True
|
||||
executor_kwargs["capabilities"] = dict(DesiredCapabilities.CHROME.items())
|
||||
if kwargs["binary"] is not None:
|
||||
executor_kwargs["capabilities"]["chromeOptions"] = {"binary": kwargs["binary"]}
|
||||
|
||||
return executor_kwargs
|
||||
|
||||
|
||||
def env_options():
|
||||
return {"host": "web-platform.test",
|
||||
"bind_hostname": "true"}
|
||||
|
||||
|
||||
class ChromeBrowser(Browser):
|
||||
"""Chrome is backed by chromedriver, which is supplied through
|
||||
``wptrunner.webdriver.ChromeDriverServer``.
|
||||
"""
|
||||
|
||||
def __init__(self, logger, binary, webdriver_binary="chromedriver"):
|
||||
"""Creates a new representation of Chrome. The `binary` argument gives
|
||||
the browser binary to use for testing."""
|
||||
Browser.__init__(self, logger)
|
||||
self.binary = binary
|
||||
self.server = ChromeDriverServer(self.logger, binary=webdriver_binary)
|
||||
|
||||
def start(self):
|
||||
self.server.start(block=False)
|
||||
|
||||
def stop(self):
|
||||
self.server.stop()
|
||||
|
||||
def pid(self):
|
||||
return self.server.pid
|
||||
|
||||
def is_alive(self):
|
||||
# TODO(ato): This only indicates the driver is alive,
|
||||
# and doesn't say anything about whether a browser session
|
||||
# is active.
|
||||
return self.server.is_alive()
|
||||
|
||||
def cleanup(self):
|
||||
self.stop()
|
||||
|
||||
def executor_browser(self):
|
||||
return ExecutorBrowser, {"webdriver_url": self.server.url}
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import mozinfo
|
||||
from mozprocess import ProcessHandler
|
||||
from mozprofile import FirefoxProfile, Preferences
|
||||
from mozprofile.permissions import ServerLocations
|
||||
from mozrunner import FirefoxRunner
|
||||
from mozrunner.utils import get_stack_fixer_function
|
||||
from mozcrash import mozcrash
|
||||
|
||||
from .base import (get_free_port,
|
||||
Browser,
|
||||
ExecutorBrowser,
|
||||
require_arg,
|
||||
cmd_arg,
|
||||
browser_command)
|
||||
from ..executors import executor_kwargs as base_executor_kwargs
|
||||
from ..executors.executormarionette import (MarionetteTestharnessExecutor,
|
||||
MarionetteRefTestExecutor,
|
||||
MarionetteWdspecExecutor)
|
||||
from ..environment import hostnames
|
||||
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
__wptrunner__ = {"product": "firefox",
|
||||
"check_args": "check_args",
|
||||
"browser": "FirefoxBrowser",
|
||||
"executor": {"testharness": "MarionetteTestharnessExecutor",
|
||||
"reftest": "MarionetteRefTestExecutor",
|
||||
"wdspec": "MarionetteWdspecExecutor"},
|
||||
"browser_kwargs": "browser_kwargs",
|
||||
"executor_kwargs": "executor_kwargs",
|
||||
"env_options": "env_options",
|
||||
"run_info_extras": "run_info_extras",
|
||||
"update_properties": "update_properties"}
|
||||
|
||||
|
||||
def check_args(**kwargs):
|
||||
require_arg(kwargs, "binary")
|
||||
if kwargs["ssl_type"] != "none":
|
||||
require_arg(kwargs, "certutil_binary")
|
||||
|
||||
|
||||
def browser_kwargs(**kwargs):
|
||||
return {"binary": kwargs["binary"],
|
||||
"prefs_root": kwargs["prefs_root"],
|
||||
"debug_info": kwargs["debug_info"],
|
||||
"symbols_path": kwargs["symbols_path"],
|
||||
"stackwalk_binary": kwargs["stackwalk_binary"],
|
||||
"certutil_binary": kwargs["certutil_binary"],
|
||||
"ca_certificate_path": kwargs["ssl_env"].ca_cert_path(),
|
||||
"e10s": kwargs["gecko_e10s"],
|
||||
"stackfix_dir": kwargs["stackfix_dir"]}
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
|
||||
**kwargs):
|
||||
executor_kwargs = base_executor_kwargs(test_type, server_config,
|
||||
cache_manager, **kwargs)
|
||||
executor_kwargs["close_after_done"] = test_type != "reftest"
|
||||
if kwargs["timeout_multiplier"] is None:
|
||||
if test_type == "reftest":
|
||||
if run_info_data["debug"] or run_info_data.get("asan"):
|
||||
executor_kwargs["timeout_multiplier"] = 4
|
||||
else:
|
||||
executor_kwargs["timeout_multiplier"] = 2
|
||||
elif run_info_data["debug"] or run_info_data.get("asan"):
|
||||
executor_kwargs["timeout_multiplier"] = 3
|
||||
if test_type == "wdspec":
|
||||
executor_kwargs["webdriver_binary"] = kwargs.get("webdriver_binary")
|
||||
return executor_kwargs
|
||||
|
||||
|
||||
def env_options():
|
||||
return {"host": "127.0.0.1",
|
||||
"external_host": "web-platform.test",
|
||||
"bind_hostname": "false",
|
||||
"certificate_domain": "web-platform.test",
|
||||
"supports_debugger": True}
|
||||
|
||||
|
||||
def run_info_extras(**kwargs):
|
||||
return {"e10s": kwargs["gecko_e10s"]}
|
||||
|
||||
|
||||
def update_properties():
|
||||
return ["debug", "e10s", "os", "version", "processor", "bits"], {"debug", "e10s"}
|
||||
|
||||
|
||||
class FirefoxBrowser(Browser):
|
||||
used_ports = set()
|
||||
init_timeout = 60
|
||||
|
||||
def __init__(self, logger, binary, prefs_root, debug_info=None,
|
||||
symbols_path=None, stackwalk_binary=None, certutil_binary=None,
|
||||
ca_certificate_path=None, e10s=False, stackfix_dir=None):
|
||||
Browser.__init__(self, logger)
|
||||
self.binary = binary
|
||||
self.prefs_root = prefs_root
|
||||
self.marionette_port = None
|
||||
self.runner = None
|
||||
self.debug_info = debug_info
|
||||
self.profile = None
|
||||
self.symbols_path = symbols_path
|
||||
self.stackwalk_binary = stackwalk_binary
|
||||
self.ca_certificate_path = ca_certificate_path
|
||||
self.certutil_binary = certutil_binary
|
||||
self.e10s = e10s
|
||||
if self.symbols_path and stackfix_dir:
|
||||
self.stack_fixer = get_stack_fixer_function(stackfix_dir,
|
||||
self.symbols_path)
|
||||
else:
|
||||
self.stack_fixer = None
|
||||
|
||||
def start(self):
|
||||
self.marionette_port = get_free_port(2828, exclude=self.used_ports)
|
||||
self.used_ports.add(self.marionette_port)
|
||||
|
||||
env = os.environ.copy()
|
||||
env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
|
||||
|
||||
locations = ServerLocations(filename=os.path.join(here, "server-locations.txt"))
|
||||
|
||||
preferences = self.load_prefs()
|
||||
|
||||
self.profile = FirefoxProfile(locations=locations,
|
||||
preferences=preferences)
|
||||
self.profile.set_preferences({"marionette.defaultPrefs.enabled": True,
|
||||
"marionette.defaultPrefs.port": self.marionette_port,
|
||||
"dom.disable_open_during_load": False,
|
||||
"network.dns.localDomains": ",".join(hostnames),
|
||||
"network.proxy.type": 0,
|
||||
"places.history.enabled": False})
|
||||
if self.e10s:
|
||||
self.profile.set_preferences({"browser.tabs.remote.autostart": True})
|
||||
|
||||
if self.ca_certificate_path is not None:
|
||||
self.setup_ssl()
|
||||
|
||||
debug_args, cmd = browser_command(self.binary, [cmd_arg("marionette"), "about:blank"],
|
||||
self.debug_info)
|
||||
|
||||
self.runner = FirefoxRunner(profile=self.profile,
|
||||
binary=cmd[0],
|
||||
cmdargs=cmd[1:],
|
||||
env=env,
|
||||
process_class=ProcessHandler,
|
||||
process_args={"processOutputLine": [self.on_output]})
|
||||
|
||||
self.logger.debug("Starting Firefox")
|
||||
|
||||
self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
|
||||
self.logger.debug("Firefox Started")
|
||||
|
||||
def load_prefs(self):
|
||||
prefs_path = os.path.join(self.prefs_root, "prefs_general.js")
|
||||
if os.path.exists(prefs_path):
|
||||
preferences = Preferences.read_prefs(prefs_path)
|
||||
else:
|
||||
self.logger.warning("Failed to find base prefs file in %s" % prefs_path)
|
||||
preferences = []
|
||||
|
||||
return preferences
|
||||
|
||||
def stop(self):
|
||||
self.logger.debug("Stopping browser")
|
||||
if self.runner is not None:
|
||||
try:
|
||||
self.runner.stop()
|
||||
except OSError:
|
||||
# This can happen on Windows if the process is already dead
|
||||
pass
|
||||
|
||||
def pid(self):
|
||||
if self.runner.process_handler is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.runner.process_handler.pid
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def on_output(self, line):
|
||||
"""Write a line of output from the firefox process to the log"""
|
||||
data = line.decode("utf8", "replace")
|
||||
if self.stack_fixer:
|
||||
data = self.stack_fixer(data)
|
||||
self.logger.process_output(self.pid(),
|
||||
data,
|
||||
command=" ".join(self.runner.command))
|
||||
|
||||
def is_alive(self):
|
||||
if self.runner:
|
||||
return self.runner.is_running()
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
self.stop()
|
||||
|
||||
def executor_browser(self):
|
||||
assert self.marionette_port is not None
|
||||
return ExecutorBrowser, {"marionette_port": self.marionette_port}
|
||||
|
||||
def log_crash(self, process, test):
|
||||
dump_dir = os.path.join(self.profile.profile, "minidumps")
|
||||
|
||||
mozcrash.log_crashes(self.logger,
|
||||
dump_dir,
|
||||
symbols_path=self.symbols_path,
|
||||
stackwalk_binary=self.stackwalk_binary,
|
||||
process=process,
|
||||
test=test)
|
||||
|
||||
def setup_ssl(self):
|
||||
"""Create a certificate database to use in the test profile. This is configured
|
||||
to trust the CA Certificate that has signed the web-platform.test server
|
||||
certificate."""
|
||||
|
||||
self.logger.info("Setting up ssl")
|
||||
|
||||
# Make sure the certutil libraries from the source tree are loaded when using a
|
||||
# local copy of certutil
|
||||
# TODO: Maybe only set this if certutil won't launch?
|
||||
env = os.environ.copy()
|
||||
certutil_dir = os.path.dirname(self.binary)
|
||||
if mozinfo.isMac:
|
||||
env_var = "DYLD_LIBRARY_PATH"
|
||||
elif mozinfo.isUnix:
|
||||
env_var = "LD_LIBRARY_PATH"
|
||||
else:
|
||||
env_var = "PATH"
|
||||
|
||||
|
||||
env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]])
|
||||
if env_var in env else certutil_dir).encode(
|
||||
sys.getfilesystemencoding() or 'utf-8', 'replace')
|
||||
|
||||
def certutil(*args):
|
||||
cmd = [self.certutil_binary] + list(args)
|
||||
self.logger.process_output("certutil",
|
||||
subprocess.check_output(cmd,
|
||||
env=env,
|
||||
stderr=subprocess.STDOUT),
|
||||
" ".join(cmd))
|
||||
|
||||
pw_path = os.path.join(self.profile.profile, ".crtdbpw")
|
||||
with open(pw_path, "w") as f:
|
||||
# Use empty password for certificate db
|
||||
f.write("\n")
|
||||
|
||||
cert_db_path = self.profile.profile
|
||||
|
||||
# Create a new certificate db
|
||||
certutil("-N", "-d", cert_db_path, "-f", pw_path)
|
||||
|
||||
# Add the CA certificate to the database and mark as trusted to issue server certs
|
||||
certutil("-A", "-d", cert_db_path, "-f", pw_path, "-t", "CT,,",
|
||||
"-n", "web-platform-tests", "-i", self.ca_certificate_path)
|
||||
|
||||
# List all certs in the database
|
||||
certutil("-L", "-d", cert_db_path)
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# See /build/pgo/server-locations.txt for documentation on the format
|
||||
|
||||
http://localhost:8000 primary
|
||||
|
||||
http://web-platform.test:8000
|
||||
http://www.web-platform.test:8000
|
||||
http://www1.web-platform.test:8000
|
||||
http://www2.web-platform.test:8000
|
||||
http://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8000
|
||||
http://xn--lve-6lad.web-platform.test:8000
|
||||
|
||||
http://web-platform.test:8001
|
||||
http://www.web-platform.test:8001
|
||||
http://www1.web-platform.test:8001
|
||||
http://www2.web-platform.test:8001
|
||||
http://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8001
|
||||
http://xn--lve-6lad.web-platform.test:8001
|
||||
|
||||
https://web-platform.test:8443
|
||||
https://www.web-platform.test:8443
|
||||
https://www1.web-platform.test:8443
|
||||
https://www2.web-platform.test:8443
|
||||
https://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8443
|
||||
https://xn--lve-6lad.web-platform.test:8443
|
||||
|
||||
# These are actually ws servers, but until mozprofile is
|
||||
# fixed we have to pretend that they are http servers
|
||||
http://web-platform.test:8888
|
||||
http://www.web-platform.test:8888
|
||||
http://www1.web-platform.test:8888
|
||||
http://www2.web-platform.test:8888
|
||||
http://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8888
|
||||
http://xn--lve-6lad.web-platform.test:8888
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
|
||||
from .base import NullBrowser, ExecutorBrowser, require_arg
|
||||
from ..executors import executor_kwargs as base_executor_kwargs
|
||||
from ..executors.executorservo import ServoTestharnessExecutor, ServoRefTestExecutor
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
__wptrunner__ = {"product": "servo",
|
||||
"check_args": "check_args",
|
||||
"browser": "ServoBrowser",
|
||||
"executor": {"testharness": "ServoTestharnessExecutor",
|
||||
"reftest": "ServoRefTestExecutor"},
|
||||
"browser_kwargs": "browser_kwargs",
|
||||
"executor_kwargs": "executor_kwargs",
|
||||
"env_options": "env_options",
|
||||
"run_info_extras": "run_info_extras",
|
||||
"update_properties": "update_properties"}
|
||||
|
||||
|
||||
def check_args(**kwargs):
|
||||
require_arg(kwargs, "binary")
|
||||
|
||||
|
||||
def browser_kwargs(**kwargs):
|
||||
return {"binary": kwargs["binary"],
|
||||
"debug_info": kwargs["debug_info"],
|
||||
"binary_args": kwargs["binary_args"],
|
||||
"user_stylesheets": kwargs.get("user_stylesheets"),
|
||||
"render_backend": kwargs.get("servo_backend")}
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
|
||||
**kwargs):
|
||||
rv = base_executor_kwargs(test_type, server_config,
|
||||
cache_manager, **kwargs)
|
||||
rv["pause_after_test"] = kwargs["pause_after_test"]
|
||||
return rv
|
||||
|
||||
|
||||
def env_options():
|
||||
return {"host": "127.0.0.1",
|
||||
"external_host": "web-platform.test",
|
||||
"bind_hostname": "true",
|
||||
"testharnessreport": "testharnessreport-servo.js",
|
||||
"supports_debugger": True}
|
||||
|
||||
|
||||
def run_info_extras(**kwargs):
|
||||
return {"backend": kwargs["servo_backend"]}
|
||||
|
||||
|
||||
def update_properties():
|
||||
return ["debug", "os", "version", "processor", "bits", "backend"], None
|
||||
|
||||
|
||||
def render_arg(render_backend):
|
||||
return {"cpu": "--cpu", "webrender": "-w"}[render_backend]
|
||||
|
||||
|
||||
class ServoBrowser(NullBrowser):
|
||||
def __init__(self, logger, binary, debug_info=None, binary_args=None,
|
||||
user_stylesheets=None, render_backend="cpu"):
|
||||
NullBrowser.__init__(self, logger)
|
||||
self.binary = binary
|
||||
self.debug_info = debug_info
|
||||
self.binary_args = binary_args or []
|
||||
self.user_stylesheets = user_stylesheets or []
|
||||
self.render_backend = render_backend
|
||||
|
||||
def executor_browser(self):
|
||||
return ExecutorBrowser, {"binary": self.binary,
|
||||
"debug_info": self.debug_info,
|
||||
"binary_args": self.binary_args,
|
||||
"user_stylesheets": self.user_stylesheets,
|
||||
"render_backend": self.render_backend}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from mozprocess import ProcessHandler
|
||||
|
||||
from .base import Browser, require_arg, get_free_port, browser_command, ExecutorBrowser
|
||||
from .servo import render_arg
|
||||
from ..executors import executor_kwargs as base_executor_kwargs
|
||||
from ..executors.executorservodriver import (ServoWebDriverTestharnessExecutor,
|
||||
ServoWebDriverRefTestExecutor)
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
__wptrunner__ = {"product": "servodriver",
|
||||
"check_args": "check_args",
|
||||
"browser": "ServoWebDriverBrowser",
|
||||
"executor": {"testharness": "ServoWebDriverTestharnessExecutor",
|
||||
"reftest": "ServoWebDriverRefTestExecutor"},
|
||||
"browser_kwargs": "browser_kwargs",
|
||||
"executor_kwargs": "executor_kwargs",
|
||||
"env_options": "env_options",
|
||||
"run_info_extras": "run_info_extras",
|
||||
"update_properties": "update_properties"}
|
||||
|
||||
hosts_text = """127.0.0.1 web-platform.test
|
||||
127.0.0.1 www.web-platform.test
|
||||
127.0.0.1 www1.web-platform.test
|
||||
127.0.0.1 www2.web-platform.test
|
||||
127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
|
||||
127.0.0.1 xn--lve-6lad.web-platform.test
|
||||
"""
|
||||
|
||||
|
||||
def check_args(**kwargs):
|
||||
require_arg(kwargs, "binary")
|
||||
|
||||
|
||||
def browser_kwargs(**kwargs):
|
||||
return {"binary": kwargs["binary"],
|
||||
"debug_info": kwargs["debug_info"],
|
||||
"user_stylesheets": kwargs.get("user_stylesheets"),
|
||||
"render_backend": kwargs.get("servo_backend")}
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, run_info_data, **kwargs):
|
||||
rv = base_executor_kwargs(test_type, server_config,
|
||||
cache_manager, **kwargs)
|
||||
return rv
|
||||
|
||||
|
||||
def env_options():
|
||||
return {"host": "127.0.0.1",
|
||||
"external_host": "web-platform.test",
|
||||
"bind_hostname": "true",
|
||||
"testharnessreport": "testharnessreport-servodriver.js",
|
||||
"supports_debugger": True}
|
||||
|
||||
|
||||
def run_info_extras(**kwargs):
|
||||
return {"backend": kwargs["servo_backend"]}
|
||||
|
||||
|
||||
def update_properties():
|
||||
return ["debug", "os", "version", "processor", "bits", "backend"], None
|
||||
|
||||
|
||||
def make_hosts_file():
|
||||
hosts_fd, hosts_path = tempfile.mkstemp()
|
||||
with os.fdopen(hosts_fd, "w") as f:
|
||||
f.write(hosts_text)
|
||||
return hosts_path
|
||||
|
||||
|
||||
class ServoWebDriverBrowser(Browser):
|
||||
used_ports = set()
|
||||
|
||||
def __init__(self, logger, binary, debug_info=None, webdriver_host="127.0.0.1",
|
||||
user_stylesheets=None, render_backend="cpu"):
|
||||
Browser.__init__(self, logger)
|
||||
self.binary = binary
|
||||
self.webdriver_host = webdriver_host
|
||||
self.webdriver_port = None
|
||||
self.proc = None
|
||||
self.debug_info = debug_info
|
||||
self.hosts_path = make_hosts_file()
|
||||
self.command = None
|
||||
self.user_stylesheets = user_stylesheets if user_stylesheets else []
|
||||
self.render_backend = render_backend
|
||||
|
||||
def start(self):
|
||||
self.webdriver_port = get_free_port(4444, exclude=self.used_ports)
|
||||
self.used_ports.add(self.webdriver_port)
|
||||
|
||||
env = os.environ.copy()
|
||||
env["HOST_FILE"] = self.hosts_path
|
||||
env["RUST_BACKTRACE"] = "1"
|
||||
|
||||
debug_args, command = browser_command(self.binary,
|
||||
[render_arg(self.render_backend), "--hard-fail",
|
||||
"--webdriver", str(self.webdriver_port),
|
||||
"about:blank"],
|
||||
self.debug_info)
|
||||
|
||||
for stylesheet in self.user_stylesheets:
|
||||
command += ["--user-stylesheet", stylesheet]
|
||||
|
||||
self.command = command
|
||||
|
||||
self.command = debug_args + self.command
|
||||
|
||||
if not self.debug_info or not self.debug_info.interactive:
|
||||
self.proc = ProcessHandler(self.command,
|
||||
processOutputLine=[self.on_output],
|
||||
env=env,
|
||||
storeOutput=False)
|
||||
self.proc.run()
|
||||
else:
|
||||
self.proc = subprocess.Popen(self.command, env=env)
|
||||
|
||||
self.logger.debug("Servo Started")
|
||||
|
||||
def stop(self):
|
||||
self.logger.debug("Stopping browser")
|
||||
if self.proc is not None:
|
||||
try:
|
||||
self.proc.kill()
|
||||
except OSError:
|
||||
# This can happen on Windows if the process is already dead
|
||||
pass
|
||||
|
||||
def pid(self):
|
||||
if self.proc is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.proc.pid
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def on_output(self, line):
|
||||
"""Write a line of output from the process to the log"""
|
||||
self.logger.process_output(self.pid(),
|
||||
line.decode("utf8", "replace"),
|
||||
command=" ".join(self.command))
|
||||
|
||||
def is_alive(self):
|
||||
if self.runner:
|
||||
return self.runner.is_running()
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
self.stop()
|
||||
|
||||
def executor_browser(self):
|
||||
assert self.webdriver_port is not None
|
||||
return ExecutorBrowser, {"webdriver_host": self.webdriver_host,
|
||||
"webdriver_port": self.webdriver_port}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{"host": "%(host)s",
|
||||
"ports":{"http":[8000, 8001],
|
||||
"https":[8443],
|
||||
"ws":[8888]},
|
||||
"check_subdomains":false,
|
||||
"bind_hostname":%(bind_hostname)s,
|
||||
"ssl":{}}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import ConfigParser
|
||||
import os
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
|
||||
here = os.path.split(__file__)[0]
|
||||
|
||||
class ConfigDict(dict):
|
||||
def __init__(self, base_path, *args, **kwargs):
|
||||
self.base_path = base_path
|
||||
dict.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_path(self, key, default=None):
|
||||
if key not in self:
|
||||
return default
|
||||
path = self[key]
|
||||
os.path.expanduser(path)
|
||||
return os.path.abspath(os.path.join(self.base_path, path))
|
||||
|
||||
def read(config_path):
|
||||
config_path = os.path.abspath(config_path)
|
||||
config_root = os.path.split(config_path)[0]
|
||||
parser = ConfigParser.SafeConfigParser()
|
||||
success = parser.read(config_path)
|
||||
assert config_path in success, success
|
||||
|
||||
subns = {"pwd": os.path.abspath(os.path.curdir)}
|
||||
|
||||
rv = OrderedDict()
|
||||
for section in parser.sections():
|
||||
rv[section] = ConfigDict(config_root)
|
||||
for key in parser.options(section):
|
||||
rv[section][key] = parser.get(section, key, False, subns)
|
||||
|
||||
return rv
|
||||
|
||||
def path(argv=None):
|
||||
if argv is None:
|
||||
argv = []
|
||||
path = None
|
||||
|
||||
for i, arg in enumerate(argv):
|
||||
if arg == "--config":
|
||||
if i + 1 < len(argv):
|
||||
path = argv[i + 1]
|
||||
elif arg.startswith("--config="):
|
||||
path = arg.split("=", 1)[1]
|
||||
if path is not None:
|
||||
break
|
||||
|
||||
if path is None:
|
||||
if os.path.exists("wptrunner.ini"):
|
||||
path = os.path.abspath("wptrunner.ini")
|
||||
else:
|
||||
path = os.path.join(here, "..", "wptrunner.default.ini")
|
||||
|
||||
return os.path.abspath(path)
|
||||
|
||||
def load():
|
||||
return read(path(sys.argv))
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import json
|
||||
import os
|
||||
import multiprocessing
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
from mozlog import get_default_logger, handlers
|
||||
|
||||
from wptlogging import LogLevelRewriter
|
||||
|
||||
here = os.path.split(__file__)[0]
|
||||
|
||||
serve = None
|
||||
sslutils = None
|
||||
|
||||
|
||||
hostnames = ["web-platform.test",
|
||||
"www.web-platform.test",
|
||||
"www1.web-platform.test",
|
||||
"www2.web-platform.test",
|
||||
"xn--n8j6ds53lwwkrqhv28a.web-platform.test",
|
||||
"xn--lve-6lad.web-platform.test"]
|
||||
|
||||
|
||||
def do_delayed_imports(logger, test_paths):
|
||||
global serve, sslutils
|
||||
|
||||
serve_root = serve_path(test_paths)
|
||||
sys.path.insert(0, serve_root)
|
||||
|
||||
failed = []
|
||||
|
||||
try:
|
||||
from tools.serve import serve
|
||||
except ImportError:
|
||||
failed.append("serve")
|
||||
|
||||
try:
|
||||
import sslutils
|
||||
except ImportError:
|
||||
failed.append("sslutils")
|
||||
|
||||
if failed:
|
||||
logger.critical(
|
||||
"Failed to import %s. Ensure that tests path %s contains web-platform-tests" %
|
||||
(", ".join(failed), serve_root))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def serve_path(test_paths):
|
||||
return test_paths["/"]["tests_path"]
|
||||
|
||||
|
||||
def get_ssl_kwargs(**kwargs):
|
||||
if kwargs["ssl_type"] == "openssl":
|
||||
args = {"openssl_binary": kwargs["openssl_binary"]}
|
||||
elif kwargs["ssl_type"] == "pregenerated":
|
||||
args = {"host_key_path": kwargs["host_key_path"],
|
||||
"host_cert_path": kwargs["host_cert_path"],
|
||||
"ca_cert_path": kwargs["ca_cert_path"]}
|
||||
else:
|
||||
args = {}
|
||||
return args
|
||||
|
||||
|
||||
def ssl_env(logger, **kwargs):
|
||||
ssl_env_cls = sslutils.environments[kwargs["ssl_type"]]
|
||||
return ssl_env_cls(logger, **get_ssl_kwargs(**kwargs))
|
||||
|
||||
|
||||
class TestEnvironmentError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TestEnvironment(object):
|
||||
def __init__(self, test_paths, ssl_env, pause_after_test, debug_info, options):
|
||||
"""Context manager that owns the test environment i.e. the http and
|
||||
websockets servers"""
|
||||
self.test_paths = test_paths
|
||||
self.ssl_env = ssl_env
|
||||
self.server = None
|
||||
self.config = None
|
||||
self.external_config = None
|
||||
self.pause_after_test = pause_after_test
|
||||
self.test_server_port = options.pop("test_server_port", True)
|
||||
self.debug_info = debug_info
|
||||
self.options = options if options is not None else {}
|
||||
|
||||
self.cache_manager = multiprocessing.Manager()
|
||||
self.stash = serve.stash.StashServer()
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
self.stash.__enter__()
|
||||
self.ssl_env.__enter__()
|
||||
self.cache_manager.__enter__()
|
||||
self.setup_server_logging()
|
||||
self.config = self.load_config()
|
||||
serve.set_computed_defaults(self.config)
|
||||
self.external_config, self.servers = serve.start(self.config, self.ssl_env,
|
||||
self.get_routes())
|
||||
if self.options.get("supports_debugger") and self.debug_info and self.debug_info.interactive:
|
||||
self.ignore_interrupts()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.process_interrupts()
|
||||
for scheme, servers in self.servers.iteritems():
|
||||
for port, server in servers:
|
||||
server.kill()
|
||||
self.cache_manager.__exit__(exc_type, exc_val, exc_tb)
|
||||
self.ssl_env.__exit__(exc_type, exc_val, exc_tb)
|
||||
self.stash.__exit__()
|
||||
|
||||
def ignore_interrupts(self):
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
def process_interrupts(self):
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
|
||||
def load_config(self):
|
||||
default_config_path = os.path.join(serve_path(self.test_paths), "config.default.json")
|
||||
local_config_path = os.path.join(here, "config.json")
|
||||
|
||||
with open(default_config_path) as f:
|
||||
default_config = json.load(f)
|
||||
|
||||
with open(local_config_path) as f:
|
||||
data = f.read()
|
||||
local_config = json.loads(data % self.options)
|
||||
|
||||
#TODO: allow non-default configuration for ssl
|
||||
|
||||
local_config["external_host"] = self.options.get("external_host", None)
|
||||
local_config["ssl"]["encrypt_after_connect"] = self.options.get("encrypt_after_connect", False)
|
||||
|
||||
config = serve.merge_json(default_config, local_config)
|
||||
config["doc_root"] = serve_path(self.test_paths)
|
||||
|
||||
if not self.ssl_env.ssl_enabled:
|
||||
config["ports"]["https"] = [None]
|
||||
|
||||
host = self.options.get("certificate_domain", config["host"])
|
||||
hosts = [host]
|
||||
hosts.extend("%s.%s" % (item[0], host) for item in serve.get_subdomains(host).values())
|
||||
key_file, certificate = self.ssl_env.host_cert_path(hosts)
|
||||
|
||||
config["key_file"] = key_file
|
||||
config["certificate"] = certificate
|
||||
|
||||
return config
|
||||
|
||||
def setup_server_logging(self):
|
||||
server_logger = get_default_logger(component="wptserve")
|
||||
assert server_logger is not None
|
||||
log_filter = handlers.LogLevelFilter(lambda x:x, "info")
|
||||
# Downgrade errors to warnings for the server
|
||||
log_filter = LogLevelRewriter(log_filter, ["error"], "warning")
|
||||
server_logger.component_filter = log_filter
|
||||
|
||||
try:
|
||||
#Set as the default logger for wptserve
|
||||
serve.set_logger(server_logger)
|
||||
serve.logger = server_logger
|
||||
except Exception:
|
||||
# This happens if logging has already been set up for wptserve
|
||||
pass
|
||||
|
||||
def get_routes(self):
|
||||
route_builder = serve.RoutesBuilder()
|
||||
|
||||
for path, format_args, content_type, route in [
|
||||
("testharness_runner.html", {}, "text/html", "/testharness_runner.html"),
|
||||
(self.options.get("testharnessreport", "testharnessreport.js"),
|
||||
{"output": self.pause_after_test}, "text/javascript",
|
||||
"/resources/testharnessreport.js")]:
|
||||
path = os.path.normpath(os.path.join(here, path))
|
||||
route_builder.add_static(path, format_args, content_type, route)
|
||||
|
||||
for url_base, paths in self.test_paths.iteritems():
|
||||
if url_base == "/":
|
||||
continue
|
||||
route_builder.add_mount_point(url_base, paths["tests_path"])
|
||||
|
||||
if "/" not in self.test_paths:
|
||||
del route_builder.mountpoint_routes["/"]
|
||||
|
||||
return route_builder.get_routes()
|
||||
|
||||
def ensure_started(self):
|
||||
# Pause for a while to ensure that the server has a chance to start
|
||||
time.sleep(2)
|
||||
for scheme, servers in self.servers.iteritems():
|
||||
for port, server in servers:
|
||||
if self.test_server_port:
|
||||
s = socket.socket()
|
||||
try:
|
||||
s.connect((self.config["host"], port))
|
||||
except socket.error:
|
||||
raise EnvironmentError(
|
||||
"%s server on port %d failed to start" % (scheme, port))
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
if not server.is_alive():
|
||||
raise EnvironmentError("%s server on port %d failed to start" % (scheme, port))
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from base import (executor_kwargs,
|
||||
testharness_result_converter,
|
||||
reftest_result_converter,
|
||||
TestExecutor)
|
||||
|
|
@ -1,329 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
import urlparse
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from ..testrunner import Stop
|
||||
|
||||
here = os.path.split(__file__)[0]
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
|
||||
timeout_multiplier = kwargs["timeout_multiplier"]
|
||||
if timeout_multiplier is None:
|
||||
timeout_multiplier = 1
|
||||
|
||||
executor_kwargs = {"server_config": server_config,
|
||||
"timeout_multiplier": timeout_multiplier,
|
||||
"debug_info": kwargs["debug_info"]}
|
||||
|
||||
if test_type == "reftest":
|
||||
executor_kwargs["screenshot_cache"] = cache_manager.dict()
|
||||
|
||||
return executor_kwargs
|
||||
|
||||
|
||||
def strip_server(url):
|
||||
"""Remove the scheme and netloc from a url, leaving only the path and any query
|
||||
or fragment.
|
||||
|
||||
url - the url to strip
|
||||
|
||||
e.g. http://example.org:8000/tests?id=1#2 becomes /tests?id=1#2"""
|
||||
|
||||
url_parts = list(urlparse.urlsplit(url))
|
||||
url_parts[0] = ""
|
||||
url_parts[1] = ""
|
||||
return urlparse.urlunsplit(url_parts)
|
||||
|
||||
|
||||
class TestharnessResultConverter(object):
|
||||
harness_codes = {0: "OK",
|
||||
1: "ERROR",
|
||||
2: "TIMEOUT"}
|
||||
|
||||
test_codes = {0: "PASS",
|
||||
1: "FAIL",
|
||||
2: "TIMEOUT",
|
||||
3: "NOTRUN"}
|
||||
|
||||
def __call__(self, test, result):
|
||||
"""Convert a JSON result into a (TestResult, [SubtestResult]) tuple"""
|
||||
result_url, status, message, stack, subtest_results = result
|
||||
assert result_url == test.url, ("Got results from %s, expected %s" %
|
||||
(result_url, test.url))
|
||||
harness_result = test.result_cls(self.harness_codes[status], message)
|
||||
return (harness_result,
|
||||
[test.subtest_result_cls(name, self.test_codes[status], message, stack)
|
||||
for name, status, message, stack in subtest_results])
|
||||
|
||||
|
||||
testharness_result_converter = TestharnessResultConverter()
|
||||
|
||||
|
||||
def reftest_result_converter(self, test, result):
|
||||
return (test.result_cls(result["status"], result["message"],
|
||||
extra=result.get("extra")), [])
|
||||
|
||||
|
||||
def pytest_result_converter(self, test, data):
|
||||
harness_data, subtest_data = data
|
||||
|
||||
if subtest_data is None:
|
||||
subtest_data = []
|
||||
|
||||
harness_result = test.result_cls(*harness_data)
|
||||
subtest_results = [test.subtest_result_cls(*item) for item in subtest_data]
|
||||
|
||||
return (harness_result, subtest_results)
|
||||
|
||||
|
||||
class ExecutorException(Exception):
|
||||
def __init__(self, status, message):
|
||||
self.status = status
|
||||
self.message = message
|
||||
|
||||
|
||||
class TestExecutor(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
test_type = None
|
||||
convert_result = None
|
||||
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
debug_info=None):
|
||||
"""Abstract Base class for object that actually executes the tests in a
|
||||
specific browser. Typically there will be a different TestExecutor
|
||||
subclass for each test type and method of executing tests.
|
||||
|
||||
:param browser: ExecutorBrowser instance providing properties of the
|
||||
browser that will be tested.
|
||||
:param server_config: Dictionary of wptserve server configuration of the
|
||||
form stored in TestEnvironment.external_config
|
||||
:param timeout_multiplier: Multiplier relative to base timeout to use
|
||||
when setting test timeout.
|
||||
"""
|
||||
self.runner = None
|
||||
self.browser = browser
|
||||
self.server_config = server_config
|
||||
self.timeout_multiplier = timeout_multiplier
|
||||
self.debug_info = debug_info
|
||||
self.last_environment = {"protocol": "http",
|
||||
"prefs": {}}
|
||||
self.protocol = None # This must be set in subclasses
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
"""StructuredLogger for this executor"""
|
||||
if self.runner is not None:
|
||||
return self.runner.logger
|
||||
|
||||
def setup(self, runner):
|
||||
"""Run steps needed before tests can be started e.g. connecting to
|
||||
browser instance
|
||||
|
||||
:param runner: TestRunner instance that is going to run the tests"""
|
||||
self.runner = runner
|
||||
if self.protocol is not None:
|
||||
self.protocol.setup(runner)
|
||||
|
||||
def teardown(self):
|
||||
"""Run cleanup steps after tests have finished"""
|
||||
if self.protocol is not None:
|
||||
self.protocol.teardown()
|
||||
|
||||
def run_test(self, test):
|
||||
"""Run a particular test.
|
||||
|
||||
:param test: The test to run"""
|
||||
if test.environment != self.last_environment:
|
||||
self.on_environment_change(test.environment)
|
||||
|
||||
try:
|
||||
result = self.do_test(test)
|
||||
except Exception as e:
|
||||
result = self.result_from_exception(test, e)
|
||||
|
||||
if result is Stop:
|
||||
return result
|
||||
|
||||
# log result of parent test
|
||||
if result[0].status == "ERROR":
|
||||
self.logger.debug(result[0].message)
|
||||
|
||||
self.last_environment = test.environment
|
||||
|
||||
self.runner.send_message("test_ended", test, result)
|
||||
|
||||
def server_url(self, protocol):
|
||||
return "%s://%s:%s" % (protocol,
|
||||
self.server_config["host"],
|
||||
self.server_config["ports"][protocol][0])
|
||||
|
||||
def test_url(self, test):
|
||||
return urlparse.urljoin(self.server_url(test.environment["protocol"]), test.url)
|
||||
|
||||
@abstractmethod
|
||||
def do_test(self, test):
|
||||
"""Test-type and protocol specific implementation of running a
|
||||
specific test.
|
||||
|
||||
:param test: The test to run."""
|
||||
pass
|
||||
|
||||
def on_environment_change(self, new_environment):
|
||||
pass
|
||||
|
||||
def result_from_exception(self, test, e):
|
||||
if hasattr(e, "status") and e.status in test.result_cls.statuses:
|
||||
status = e.status
|
||||
else:
|
||||
status = "ERROR"
|
||||
message = unicode(getattr(e, "message", ""))
|
||||
if message:
|
||||
message += "\n"
|
||||
message += traceback.format_exc(e)
|
||||
return test.result_cls(status, message), []
|
||||
|
||||
|
||||
class TestharnessExecutor(TestExecutor):
|
||||
convert_result = testharness_result_converter
|
||||
|
||||
|
||||
class RefTestExecutor(TestExecutor):
|
||||
convert_result = reftest_result_converter
|
||||
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1, screenshot_cache=None,
|
||||
debug_info=None):
|
||||
TestExecutor.__init__(self, browser, server_config,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
|
||||
self.screenshot_cache = screenshot_cache
|
||||
|
||||
|
||||
class RefTestImplementation(object):
|
||||
def __init__(self, executor):
|
||||
self.timeout_multiplier = executor.timeout_multiplier
|
||||
self.executor = executor
|
||||
# Cache of url:(screenshot hash, screenshot). Typically the
|
||||
# screenshot is None, but we set this value if a test fails
|
||||
# and the screenshot was taken from the cache so that we may
|
||||
# retrieve the screenshot from the cache directly in the future
|
||||
self.screenshot_cache = self.executor.screenshot_cache
|
||||
self.message = None
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
return self.executor.logger
|
||||
|
||||
def get_hash(self, test, viewport_size, dpi):
|
||||
timeout = test.timeout * self.timeout_multiplier
|
||||
key = (test.url, viewport_size, dpi)
|
||||
|
||||
if key not in self.screenshot_cache:
|
||||
success, data = self.executor.screenshot(test, viewport_size, dpi)
|
||||
|
||||
if not success:
|
||||
return False, data
|
||||
|
||||
screenshot = data
|
||||
hash_value = hashlib.sha1(screenshot).hexdigest()
|
||||
|
||||
self.screenshot_cache[key] = (hash_value, None)
|
||||
|
||||
rv = (hash_value, screenshot)
|
||||
else:
|
||||
rv = self.screenshot_cache[key]
|
||||
|
||||
self.message.append("%s %s" % (test.url, rv[0]))
|
||||
return True, rv
|
||||
|
||||
def is_pass(self, lhs_hash, rhs_hash, relation):
|
||||
assert relation in ("==", "!=")
|
||||
self.message.append("Testing %s %s %s" % (lhs_hash, relation, rhs_hash))
|
||||
return ((relation == "==" and lhs_hash == rhs_hash) or
|
||||
(relation == "!=" and lhs_hash != rhs_hash))
|
||||
|
||||
def run_test(self, test):
|
||||
viewport_size = test.viewport_size
|
||||
dpi = test.dpi
|
||||
self.message = []
|
||||
|
||||
# Depth-first search of reference tree, with the goal
|
||||
# of reachings a leaf node with only pass results
|
||||
|
||||
stack = list(((test, item[0]), item[1]) for item in reversed(test.references))
|
||||
while stack:
|
||||
hashes = [None, None]
|
||||
screenshots = [None, None]
|
||||
|
||||
nodes, relation = stack.pop()
|
||||
|
||||
for i, node in enumerate(nodes):
|
||||
success, data = self.get_hash(node, viewport_size, dpi)
|
||||
if success is False:
|
||||
return {"status": data[0], "message": data[1]}
|
||||
|
||||
hashes[i], screenshots[i] = data
|
||||
|
||||
if self.is_pass(hashes[0], hashes[1], relation):
|
||||
if nodes[1].references:
|
||||
stack.extend(list(((nodes[1], item[0]), item[1]) for item in reversed(nodes[1].references)))
|
||||
else:
|
||||
# We passed
|
||||
return {"status":"PASS", "message": None}
|
||||
|
||||
# We failed, so construct a failure message
|
||||
|
||||
for i, (node, screenshot) in enumerate(zip(nodes, screenshots)):
|
||||
if screenshot is None:
|
||||
success, screenshot = self.retake_screenshot(node, viewport_size, dpi)
|
||||
if success:
|
||||
screenshots[i] = screenshot
|
||||
|
||||
log_data = [{"url": nodes[0].url, "screenshot": screenshots[0]}, relation,
|
||||
{"url": nodes[1].url, "screenshot": screenshots[1]}]
|
||||
|
||||
return {"status": "FAIL",
|
||||
"message": "\n".join(self.message),
|
||||
"extra": {"reftest_screenshots": log_data}}
|
||||
|
||||
def retake_screenshot(self, node, viewport_size, dpi):
|
||||
success, data = self.executor.screenshot(node, viewport_size, dpi)
|
||||
if not success:
|
||||
return False, data
|
||||
|
||||
key = (node.url, viewport_size, dpi)
|
||||
hash_val, _ = self.screenshot_cache[key]
|
||||
self.screenshot_cache[key] = hash_val, data
|
||||
return True, data
|
||||
|
||||
|
||||
class WdspecExecutor(TestExecutor):
|
||||
convert_result = pytest_result_converter
|
||||
|
||||
|
||||
class Protocol(object):
|
||||
def __init__(self, executor, browser):
|
||||
self.executor = executor
|
||||
self.browser = browser
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
return self.executor.logger
|
||||
|
||||
def setup(self, runner):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
pass
|
||||
|
||||
def wait(self):
|
||||
pass
|
||||
|
|
@ -1,595 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import hashlib
|
||||
import httplib
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import urlparse
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
|
||||
from ..wpttest import WdspecResult, WdspecSubtestResult
|
||||
|
||||
errors = None
|
||||
marionette = None
|
||||
webdriver = None
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
from . import pytestrunner
|
||||
from .base import (ExecutorException,
|
||||
Protocol,
|
||||
RefTestExecutor,
|
||||
RefTestImplementation,
|
||||
TestExecutor,
|
||||
TestharnessExecutor,
|
||||
testharness_result_converter,
|
||||
reftest_result_converter,
|
||||
strip_server,
|
||||
WdspecExecutor)
|
||||
from ..testrunner import Stop
|
||||
from ..webdriver_server import GeckoDriverServer
|
||||
|
||||
# Extra timeout to use after internal test timeout at which the harness
|
||||
# should force a timeout
|
||||
extra_timeout = 5 # seconds
|
||||
|
||||
|
||||
def do_delayed_imports():
|
||||
global errors, marionette, webdriver
|
||||
|
||||
# Marionette client used to be called marionette, recently it changed
|
||||
# to marionette_driver for unfathomable reasons
|
||||
try:
|
||||
import marionette
|
||||
from marionette import errors
|
||||
except ImportError:
|
||||
from marionette_driver import marionette, errors
|
||||
|
||||
import webdriver
|
||||
|
||||
|
||||
class MarionetteProtocol(Protocol):
|
||||
def __init__(self, executor, browser):
|
||||
do_delayed_imports()
|
||||
|
||||
Protocol.__init__(self, executor, browser)
|
||||
self.marionette = None
|
||||
self.marionette_port = browser.marionette_port
|
||||
self.timeout = None
|
||||
self.runner_handle = None
|
||||
|
||||
def setup(self, runner):
|
||||
"""Connect to browser via Marionette."""
|
||||
Protocol.setup(self, runner)
|
||||
|
||||
self.logger.debug("Connecting to Marionette on port %i" % self.marionette_port)
|
||||
self.marionette = marionette.Marionette(host='localhost',
|
||||
port=self.marionette_port,
|
||||
socket_timeout=None)
|
||||
|
||||
# XXX Move this timeout somewhere
|
||||
self.logger.debug("Waiting for Marionette connection")
|
||||
while True:
|
||||
success = self.marionette.wait_for_port(60)
|
||||
#When running in a debugger wait indefinitely for firefox to start
|
||||
if success or self.executor.debug_info is None:
|
||||
break
|
||||
|
||||
session_started = False
|
||||
if success:
|
||||
try:
|
||||
self.logger.debug("Starting Marionette session")
|
||||
self.marionette.start_session()
|
||||
except Exception as e:
|
||||
self.logger.warning("Starting marionette session failed: %s" % e)
|
||||
else:
|
||||
self.logger.debug("Marionette session started")
|
||||
session_started = True
|
||||
|
||||
if not success or not session_started:
|
||||
self.logger.warning("Failed to connect to Marionette")
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
try:
|
||||
self.after_connect()
|
||||
except Exception:
|
||||
self.logger.warning("Post-connection steps failed")
|
||||
self.logger.error(traceback.format_exc())
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
self.executor.runner.send_message("init_succeeded")
|
||||
|
||||
def teardown(self):
|
||||
try:
|
||||
self.marionette.delete_session()
|
||||
except Exception:
|
||||
# This is typically because the session never started
|
||||
pass
|
||||
del self.marionette
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
"""Check if the Marionette connection is still active."""
|
||||
try:
|
||||
self.marionette.current_window_handle
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def after_connect(self):
|
||||
self.load_runner("http")
|
||||
|
||||
def set_timeout(self, timeout):
|
||||
"""Set the Marionette script timeout.
|
||||
|
||||
:param timeout: Script timeout in seconds
|
||||
|
||||
"""
|
||||
self.marionette.timeout.script = timeout
|
||||
self.timeout = timeout
|
||||
|
||||
def load_runner(self, protocol):
|
||||
# Check if we previously had a test window open, and if we did make sure it's closed
|
||||
self.marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
|
||||
url = urlparse.urljoin(self.executor.server_url(protocol), "/testharness_runner.html")
|
||||
self.logger.debug("Loading %s" % url)
|
||||
self.runner_handle = self.marionette.current_window_handle
|
||||
try:
|
||||
self.marionette.navigate(url)
|
||||
except Exception as e:
|
||||
self.logger.critical(
|
||||
"Loading initial page %s failed. Ensure that the "
|
||||
"there are no other programs bound to this port and "
|
||||
"that your firewall rules or network setup does not "
|
||||
"prevent access.\e%s" % (url, traceback.format_exc(e)))
|
||||
self.marionette.execute_script(
|
||||
"document.title = '%s'" % threading.current_thread().name.replace("'", '"'))
|
||||
|
||||
def close_old_windows(self, protocol):
|
||||
handles = self.marionette.window_handles
|
||||
runner_handle = None
|
||||
try:
|
||||
handles.remove(self.runner_handle)
|
||||
runner_handle = self.runner_handle
|
||||
except ValueError:
|
||||
# The runner window probably changed id but we can restore it
|
||||
# This isn't supposed to happen, but marionette ids are not yet stable
|
||||
# We assume that the first handle returned corresponds to the runner,
|
||||
# but it hopefully doesn't matter too much if that assumption is
|
||||
# wrong since we reload the runner in that tab anyway.
|
||||
runner_handle = handles.pop(0)
|
||||
|
||||
for handle in handles:
|
||||
self.marionette.switch_to_window(handle)
|
||||
self.marionette.close()
|
||||
|
||||
self.marionette.switch_to_window(runner_handle)
|
||||
if runner_handle != self.runner_handle:
|
||||
self.load_runner(protocol)
|
||||
|
||||
def wait(self):
|
||||
socket_timeout = self.marionette.client.sock.gettimeout()
|
||||
if socket_timeout:
|
||||
self.marionette.timeout.script = socket_timeout / 2
|
||||
|
||||
while True:
|
||||
try:
|
||||
self.marionette.execute_async_script("")
|
||||
except errors.ScriptTimeoutException:
|
||||
self.logger.debug("Script timed out")
|
||||
pass
|
||||
except (socket.timeout, IOError):
|
||||
self.logger.debug("Socket closed")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(traceback.format_exc(e))
|
||||
break
|
||||
|
||||
def on_environment_change(self, old_environment, new_environment):
|
||||
#Unset all the old prefs
|
||||
for name in old_environment.get("prefs", {}).iterkeys():
|
||||
value = self.executor.original_pref_values[name]
|
||||
if value is None:
|
||||
self.clear_user_pref(name)
|
||||
else:
|
||||
self.set_pref(name, value)
|
||||
|
||||
for name, value in new_environment.get("prefs", {}).iteritems():
|
||||
self.executor.original_pref_values[name] = self.get_pref(name)
|
||||
self.set_pref(name, value)
|
||||
|
||||
def set_pref(self, name, value):
|
||||
if value.lower() not in ("true", "false"):
|
||||
try:
|
||||
int(value)
|
||||
except ValueError:
|
||||
value = "'%s'" % value
|
||||
else:
|
||||
value = value.lower()
|
||||
|
||||
self.logger.info("Setting pref %s (%s)" % (name, value))
|
||||
|
||||
script = """
|
||||
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
let pref = '%s';
|
||||
let type = prefInterface.getPrefType(pref);
|
||||
let value = %s;
|
||||
switch(type) {
|
||||
case prefInterface.PREF_STRING:
|
||||
prefInterface.setCharPref(pref, value);
|
||||
break;
|
||||
case prefInterface.PREF_BOOL:
|
||||
prefInterface.setBoolPref(pref, value);
|
||||
break;
|
||||
case prefInterface.PREF_INT:
|
||||
prefInterface.setIntPref(pref, value);
|
||||
break;
|
||||
}
|
||||
""" % (name, value)
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
def clear_user_pref(self, name):
|
||||
self.logger.info("Clearing pref %s" % (name))
|
||||
script = """
|
||||
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
let pref = '%s';
|
||||
prefInterface.clearUserPref(pref);
|
||||
""" % name
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
def get_pref(self, name):
|
||||
script = """
|
||||
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
let pref = '%s';
|
||||
let type = prefInterface.getPrefType(pref);
|
||||
switch(type) {
|
||||
case prefInterface.PREF_STRING:
|
||||
return prefInterface.getCharPref(pref);
|
||||
case prefInterface.PREF_BOOL:
|
||||
return prefInterface.getBoolPref(pref);
|
||||
case prefInterface.PREF_INT:
|
||||
return prefInterface.getIntPref(pref);
|
||||
case prefInterface.PREF_INVALID:
|
||||
return null;
|
||||
}
|
||||
""" % name
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
|
||||
class RemoteMarionetteProtocol(Protocol):
|
||||
def __init__(self, executor, browser):
|
||||
do_delayed_imports()
|
||||
Protocol.__init__(self, executor, browser)
|
||||
self.session = None
|
||||
self.webdriver_binary = executor.webdriver_binary
|
||||
self.marionette_port = browser.marionette_port
|
||||
self.server = None
|
||||
|
||||
def setup(self, runner):
|
||||
"""Connect to browser via the Marionette HTTP server."""
|
||||
try:
|
||||
self.server = GeckoDriverServer(
|
||||
self.logger, self.marionette_port, binary=self.webdriver_binary)
|
||||
self.server.start(block=False)
|
||||
self.logger.info(
|
||||
"WebDriver HTTP server listening at %s" % self.server.url)
|
||||
|
||||
self.logger.info(
|
||||
"Establishing new WebDriver session with %s" % self.server.url)
|
||||
self.session = webdriver.Session(
|
||||
self.server.host, self.server.port, self.server.base_path)
|
||||
except Exception:
|
||||
self.logger.error(traceback.format_exc())
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
self.executor.runner.send_message("init_succeeded")
|
||||
|
||||
def teardown(self):
|
||||
try:
|
||||
if self.session.session_id is not None:
|
||||
self.session.end()
|
||||
except Exception:
|
||||
pass
|
||||
if self.server is not None and self.server.is_alive:
|
||||
self.server.stop()
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
"""Test that the Marionette connection is still alive.
|
||||
|
||||
Because the remote communication happens over HTTP we need to
|
||||
make an explicit request to the remote. It is allowed for
|
||||
WebDriver spec tests to not have a WebDriver session, since this
|
||||
may be what is tested.
|
||||
|
||||
An HTTP request to an invalid path that results in a 404 is
|
||||
proof enough to us that the server is alive and kicking.
|
||||
"""
|
||||
conn = httplib.HTTPConnection(self.server.host, self.server.port)
|
||||
conn.request("HEAD", self.server.base_path + "invalid")
|
||||
res = conn.getresponse()
|
||||
return res.status == 404
|
||||
|
||||
|
||||
class ExecuteAsyncScriptRun(object):
|
||||
def __init__(self, logger, func, protocol, url, timeout):
|
||||
self.logger = logger
|
||||
self.result = (None, None)
|
||||
self.protocol = protocol
|
||||
self.marionette = protocol.marionette
|
||||
self.func = func
|
||||
self.url = url
|
||||
self.timeout = timeout
|
||||
self.result_flag = threading.Event()
|
||||
|
||||
def run(self):
|
||||
timeout = self.timeout
|
||||
|
||||
try:
|
||||
if timeout is not None:
|
||||
if timeout + extra_timeout != self.protocol.timeout:
|
||||
self.protocol.set_timeout(timeout + extra_timeout)
|
||||
else:
|
||||
# We just want it to never time out, really, but marionette doesn't
|
||||
# make that possible. It also seems to time out immediately if the
|
||||
# timeout is set too high. This works at least.
|
||||
self.protocol.set_timeout(2**28 - 1)
|
||||
except IOError:
|
||||
self.logger.error("Lost marionette connection before starting test")
|
||||
return Stop
|
||||
|
||||
executor = threading.Thread(target = self._run)
|
||||
executor.start()
|
||||
|
||||
if timeout is not None:
|
||||
wait_timeout = timeout + 2 * extra_timeout
|
||||
else:
|
||||
wait_timeout = None
|
||||
|
||||
flag = self.result_flag.wait(wait_timeout)
|
||||
if self.result[1] is None:
|
||||
self.logger.debug("Timed out waiting for a result")
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
return self.result
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
self.result = True, self.func(self.marionette, self.url, self.timeout)
|
||||
except errors.ScriptTimeoutException:
|
||||
self.logger.debug("Got a marionette timeout")
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
except (socket.timeout, IOError):
|
||||
# This can happen on a crash
|
||||
# Also, should check after the test if the firefox process is still running
|
||||
# and otherwise ignore any other result and set it to crash
|
||||
self.result = False, ("CRASH", None)
|
||||
except Exception as e:
|
||||
message = getattr(e, "message", "")
|
||||
if message:
|
||||
message += "\n"
|
||||
message += traceback.format_exc(e)
|
||||
self.result = False, ("ERROR", e)
|
||||
|
||||
finally:
|
||||
self.result_flag.set()
|
||||
|
||||
|
||||
class MarionetteTestharnessExecutor(TestharnessExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
close_after_done=True, debug_info=None, **kwargs):
|
||||
"""Marionette-based executor for testharness.js tests"""
|
||||
TestharnessExecutor.__init__(self, browser, server_config,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
|
||||
self.protocol = MarionetteProtocol(self, browser)
|
||||
self.script = open(os.path.join(here, "testharness_marionette.js")).read()
|
||||
self.close_after_done = close_after_done
|
||||
self.window_id = str(uuid.uuid4())
|
||||
|
||||
self.original_pref_values = {}
|
||||
|
||||
if marionette is None:
|
||||
do_delayed_imports()
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive
|
||||
|
||||
def on_environment_change(self, new_environment):
|
||||
self.protocol.on_environment_change(self.last_environment, new_environment)
|
||||
|
||||
if new_environment["protocol"] != self.last_environment["protocol"]:
|
||||
self.protocol.load_runner(new_environment["protocol"])
|
||||
|
||||
def do_test(self, test):
|
||||
timeout = (test.timeout * self.timeout_multiplier if self.debug_info is None
|
||||
else None)
|
||||
|
||||
success, data = ExecuteAsyncScriptRun(self.logger,
|
||||
self.do_testharness,
|
||||
self.protocol,
|
||||
self.test_url(test),
|
||||
timeout).run()
|
||||
if success:
|
||||
return self.convert_result(test, data)
|
||||
|
||||
return (test.result_cls(*data), [])
|
||||
|
||||
def do_testharness(self, marionette, url, timeout):
|
||||
if self.close_after_done:
|
||||
marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
|
||||
self.protocol.close_old_windows(self.protocol)
|
||||
|
||||
if timeout is not None:
|
||||
timeout_ms = str(timeout * 1000)
|
||||
else:
|
||||
timeout_ms = "null"
|
||||
|
||||
script = self.script % {"abs_url": url,
|
||||
"url": strip_server(url),
|
||||
"window_id": self.window_id,
|
||||
"timeout_multiplier": self.timeout_multiplier,
|
||||
"timeout": timeout_ms,
|
||||
"explicit_timeout": timeout is None}
|
||||
|
||||
rv = marionette.execute_async_script(script, new_sandbox=False)
|
||||
return rv
|
||||
|
||||
|
||||
class MarionetteRefTestExecutor(RefTestExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
screenshot_cache=None, close_after_done=True,
|
||||
debug_info=None, **kwargs):
|
||||
|
||||
"""Marionette-based executor for reftests"""
|
||||
RefTestExecutor.__init__(self,
|
||||
browser,
|
||||
server_config,
|
||||
screenshot_cache=screenshot_cache,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
self.protocol = MarionetteProtocol(self, browser)
|
||||
self.implementation = RefTestImplementation(self)
|
||||
self.close_after_done = close_after_done
|
||||
self.has_window = False
|
||||
self.original_pref_values = {}
|
||||
|
||||
with open(os.path.join(here, "reftest.js")) as f:
|
||||
self.script = f.read()
|
||||
with open(os.path.join(here, "reftest-wait.js")) as f:
|
||||
self.wait_script = f.read()
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive
|
||||
|
||||
def on_environment_change(self, new_environment):
|
||||
self.protocol.on_environment_change(self.last_environment, new_environment)
|
||||
|
||||
def do_test(self, test):
|
||||
if self.close_after_done and self.has_window:
|
||||
self.protocol.marionette.close()
|
||||
self.protocol.marionette.switch_to_window(
|
||||
self.protocol.marionette.window_handles[-1])
|
||||
self.has_window = False
|
||||
|
||||
if not self.has_window:
|
||||
self.protocol.marionette.execute_script(self.script)
|
||||
self.protocol.marionette.switch_to_window(self.protocol.marionette.window_handles[-1])
|
||||
self.has_window = True
|
||||
|
||||
result = self.implementation.run_test(test)
|
||||
return self.convert_result(test, result)
|
||||
|
||||
def screenshot(self, test, viewport_size, dpi):
|
||||
# https://github.com/w3c/wptrunner/issues/166
|
||||
assert viewport_size is None
|
||||
assert dpi is None
|
||||
|
||||
timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
|
||||
|
||||
test_url = self.test_url(test)
|
||||
|
||||
return ExecuteAsyncScriptRun(self.logger,
|
||||
self._screenshot,
|
||||
self.protocol,
|
||||
test_url,
|
||||
timeout).run()
|
||||
|
||||
def _screenshot(self, marionette, url, timeout):
|
||||
marionette.navigate(url)
|
||||
|
||||
marionette.execute_async_script(self.wait_script)
|
||||
|
||||
screenshot = marionette.screenshot()
|
||||
# strip off the data:img/png, part of the url
|
||||
if screenshot.startswith("data:image/png;base64,"):
|
||||
screenshot = screenshot.split(",", 1)[1]
|
||||
|
||||
return screenshot
|
||||
|
||||
|
||||
class WdspecRun(object):
|
||||
def __init__(self, func, session, path, timeout):
|
||||
self.func = func
|
||||
self.result = (None, None)
|
||||
self.session = session
|
||||
self.path = path
|
||||
self.timeout = timeout
|
||||
self.result_flag = threading.Event()
|
||||
|
||||
def run(self):
|
||||
"""Runs function in a thread and interrupts it if it exceeds the
|
||||
given timeout. Returns (True, (Result, [SubtestResult ...])) in
|
||||
case of success, or (False, (status, extra information)) in the
|
||||
event of failure.
|
||||
"""
|
||||
|
||||
executor = threading.Thread(target=self._run)
|
||||
executor.start()
|
||||
|
||||
flag = self.result_flag.wait(self.timeout)
|
||||
if self.result[1] is None:
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
|
||||
return self.result
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
self.result = True, self.func(self.session, self.path, self.timeout)
|
||||
except (socket.timeout, IOError):
|
||||
self.result = False, ("CRASH", None)
|
||||
except Exception as e:
|
||||
message = getattr(e, "message")
|
||||
if message:
|
||||
message += "\n"
|
||||
message += traceback.format_exc(e)
|
||||
self.result = False, ("ERROR", message)
|
||||
finally:
|
||||
self.result_flag.set()
|
||||
|
||||
|
||||
class MarionetteWdspecExecutor(WdspecExecutor):
|
||||
def __init__(self, browser, server_config, webdriver_binary,
|
||||
timeout_multiplier=1, close_after_done=True, debug_info=None):
|
||||
WdspecExecutor.__init__(self, browser, server_config,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
self.webdriver_binary = webdriver_binary
|
||||
self.protocol = RemoteMarionetteProtocol(self, browser)
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive
|
||||
|
||||
def on_environment_change(self, new_environment):
|
||||
pass
|
||||
|
||||
def do_test(self, test):
|
||||
timeout = test.timeout * self.timeout_multiplier + extra_timeout
|
||||
|
||||
success, data = WdspecRun(self.do_wdspec,
|
||||
self.protocol.session,
|
||||
test.abs_path,
|
||||
timeout).run()
|
||||
|
||||
if success:
|
||||
return self.convert_result(test, data)
|
||||
|
||||
return (test.result_cls(*data), [])
|
||||
|
||||
def do_wdspec(self, session, path, timeout):
|
||||
harness_result = ("OK", None)
|
||||
subtest_results = pytestrunner.run(
|
||||
path, session, self.server_url, timeout=timeout)
|
||||
return (harness_result, subtest_results)
|
||||
|
|
@ -1,271 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import urlparse
|
||||
import uuid
|
||||
|
||||
from .base import (ExecutorException,
|
||||
Protocol,
|
||||
RefTestExecutor,
|
||||
RefTestImplementation,
|
||||
TestExecutor,
|
||||
TestharnessExecutor,
|
||||
testharness_result_converter,
|
||||
reftest_result_converter,
|
||||
strip_server)
|
||||
from ..testrunner import Stop
|
||||
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
webdriver = None
|
||||
exceptions = None
|
||||
|
||||
extra_timeout = 5
|
||||
|
||||
def do_delayed_imports():
|
||||
global webdriver
|
||||
global exceptions
|
||||
from selenium import webdriver
|
||||
from selenium.common import exceptions
|
||||
|
||||
|
||||
class SeleniumProtocol(Protocol):
|
||||
def __init__(self, executor, browser, capabilities, **kwargs):
|
||||
do_delayed_imports()
|
||||
|
||||
Protocol.__init__(self, executor, browser)
|
||||
self.capabilities = capabilities
|
||||
self.url = browser.webdriver_url
|
||||
self.webdriver = None
|
||||
|
||||
def setup(self, runner):
|
||||
"""Connect to browser via Selenium's WebDriver implementation."""
|
||||
self.runner = runner
|
||||
self.logger.debug("Connecting to Selenium on URL: %s" % self.url)
|
||||
|
||||
session_started = False
|
||||
try:
|
||||
self.webdriver = webdriver.Remote(
|
||||
self.url, desired_capabilities=self.capabilities)
|
||||
except:
|
||||
self.logger.warning(
|
||||
"Connecting to Selenium failed:\n%s" % traceback.format_exc())
|
||||
else:
|
||||
self.logger.debug("Selenium session started")
|
||||
session_started = True
|
||||
|
||||
if not session_started:
|
||||
self.logger.warning("Failed to connect to Selenium")
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
try:
|
||||
self.after_connect()
|
||||
except:
|
||||
print >> sys.stderr, traceback.format_exc()
|
||||
self.logger.warning(
|
||||
"Failed to connect to navigate initial page")
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
self.executor.runner.send_message("init_succeeded")
|
||||
|
||||
def teardown(self):
|
||||
self.logger.debug("Hanging up on Selenium session")
|
||||
try:
|
||||
self.webdriver.quit()
|
||||
except:
|
||||
pass
|
||||
del self.webdriver
|
||||
|
||||
def is_alive(self):
|
||||
try:
|
||||
# Get a simple property over the connection
|
||||
self.webdriver.current_window_handle
|
||||
# TODO what exception?
|
||||
except (socket.timeout, exceptions.ErrorInResponseException):
|
||||
return False
|
||||
return True
|
||||
|
||||
def after_connect(self):
|
||||
self.load_runner("http")
|
||||
|
||||
def load_runner(self, protocol):
|
||||
url = urlparse.urljoin(self.executor.server_url(protocol),
|
||||
"/testharness_runner.html")
|
||||
self.logger.debug("Loading %s" % url)
|
||||
self.webdriver.get(url)
|
||||
self.webdriver.execute_script("document.title = '%s'" %
|
||||
threading.current_thread().name.replace("'", '"'))
|
||||
|
||||
def wait(self):
|
||||
while True:
|
||||
try:
|
||||
self.webdriver.execute_async_script("");
|
||||
except exceptions.TimeoutException:
|
||||
pass
|
||||
except (socket.timeout, exceptions.NoSuchWindowException,
|
||||
exceptions.ErrorInResponseException, IOError):
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(traceback.format_exc(e))
|
||||
break
|
||||
|
||||
|
||||
class SeleniumRun(object):
|
||||
def __init__(self, func, webdriver, url, timeout):
|
||||
self.func = func
|
||||
self.result = None
|
||||
self.webdriver = webdriver
|
||||
self.url = url
|
||||
self.timeout = timeout
|
||||
self.result_flag = threading.Event()
|
||||
|
||||
def run(self):
|
||||
timeout = self.timeout
|
||||
|
||||
try:
|
||||
self.webdriver.set_script_timeout((timeout + extra_timeout) * 1000)
|
||||
except exceptions.ErrorInResponseException:
|
||||
self.logger.error("Lost WebDriver connection")
|
||||
return Stop
|
||||
|
||||
executor = threading.Thread(target=self._run)
|
||||
executor.start()
|
||||
|
||||
flag = self.result_flag.wait(timeout + 2 * extra_timeout)
|
||||
if self.result is None:
|
||||
assert not flag
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
|
||||
return self.result
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
self.result = True, self.func(self.webdriver, self.url, self.timeout)
|
||||
except exceptions.TimeoutException:
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
except (socket.timeout, exceptions.ErrorInResponseException):
|
||||
self.result = False, ("CRASH", None)
|
||||
except Exception as e:
|
||||
message = getattr(e, "message", "")
|
||||
if message:
|
||||
message += "\n"
|
||||
message += traceback.format_exc(e)
|
||||
self.result = False, ("ERROR", e)
|
||||
finally:
|
||||
self.result_flag.set()
|
||||
|
||||
|
||||
class SeleniumTestharnessExecutor(TestharnessExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
close_after_done=True, capabilities=None, debug_info=None):
|
||||
"""Selenium-based executor for testharness.js tests"""
|
||||
TestharnessExecutor.__init__(self, browser, server_config,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
self.protocol = SeleniumProtocol(self, browser, capabilities)
|
||||
with open(os.path.join(here, "testharness_webdriver.js")) as f:
|
||||
self.script = f.read()
|
||||
self.close_after_done = close_after_done
|
||||
self.window_id = str(uuid.uuid4())
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive()
|
||||
|
||||
def on_protocol_change(self, new_protocol):
|
||||
self.protocol.load_runner(new_protocol)
|
||||
|
||||
def do_test(self, test):
|
||||
url = self.test_url(test)
|
||||
|
||||
success, data = SeleniumRun(self.do_testharness,
|
||||
self.protocol.webdriver,
|
||||
url,
|
||||
test.timeout * self.timeout_multiplier).run()
|
||||
|
||||
if success:
|
||||
return self.convert_result(test, data)
|
||||
|
||||
return (test.result_cls(*data), [])
|
||||
|
||||
def do_testharness(self, webdriver, url, timeout):
|
||||
return webdriver.execute_async_script(
|
||||
self.script % {"abs_url": url,
|
||||
"url": strip_server(url),
|
||||
"window_id": self.window_id,
|
||||
"timeout_multiplier": self.timeout_multiplier,
|
||||
"timeout": timeout * 1000})
|
||||
|
||||
class SeleniumRefTestExecutor(RefTestExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
screenshot_cache=None, close_after_done=True,
|
||||
debug_info=None, capabilities=None):
|
||||
"""Selenium WebDriver-based executor for reftests"""
|
||||
RefTestExecutor.__init__(self,
|
||||
browser,
|
||||
server_config,
|
||||
screenshot_cache=screenshot_cache,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
self.protocol = SeleniumProtocol(self, browser,
|
||||
capabilities=capabilities)
|
||||
self.implementation = RefTestImplementation(self)
|
||||
self.close_after_done = close_after_done
|
||||
self.has_window = False
|
||||
|
||||
with open(os.path.join(here, "reftest.js")) as f:
|
||||
self.script = f.read()
|
||||
with open(os.path.join(here, "reftest-wait_webdriver.js")) as f:
|
||||
self.wait_script = f.read()
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive()
|
||||
|
||||
def do_test(self, test):
|
||||
self.logger.info("Test requires OS-level window focus")
|
||||
|
||||
if self.close_after_done and self.has_window:
|
||||
self.protocol.webdriver.close()
|
||||
self.protocol.webdriver.switch_to_window(
|
||||
self.protocol.webdriver.window_handles[-1])
|
||||
self.has_window = False
|
||||
|
||||
if not self.has_window:
|
||||
self.protocol.webdriver.execute_script(self.script)
|
||||
self.protocol.webdriver.switch_to_window(
|
||||
self.protocol.webdriver.window_handles[-1])
|
||||
self.has_window = True
|
||||
|
||||
result = self.implementation.run_test(test)
|
||||
|
||||
return self.convert_result(test, result)
|
||||
|
||||
def screenshot(self, test, viewport_size, dpi):
|
||||
# https://github.com/w3c/wptrunner/issues/166
|
||||
assert viewport_size is None
|
||||
assert dpi is None
|
||||
|
||||
return SeleniumRun(self._screenshot,
|
||||
self.protocol.webdriver,
|
||||
self.test_url(test),
|
||||
test.timeout).run()
|
||||
|
||||
def _screenshot(self, webdriver, url, timeout):
|
||||
webdriver.get(url)
|
||||
|
||||
webdriver.execute_async_script(self.wait_script)
|
||||
|
||||
screenshot = webdriver.get_screenshot_as_base64()
|
||||
|
||||
# strip off the data:img/png, part of the url
|
||||
if screenshot.startswith("data:image/png;base64,"):
|
||||
screenshot = screenshot.split(",", 1)[1]
|
||||
|
||||
return screenshot
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import urlparse
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
|
||||
from mozprocess import ProcessHandler
|
||||
|
||||
from .base import (ExecutorException,
|
||||
Protocol,
|
||||
RefTestImplementation,
|
||||
testharness_result_converter,
|
||||
reftest_result_converter)
|
||||
from .process import ProcessTestExecutor
|
||||
from ..browsers.base import browser_command
|
||||
render_arg = None
|
||||
|
||||
|
||||
def do_delayed_imports():
|
||||
global render_arg
|
||||
from ..browsers.servo import render_arg
|
||||
|
||||
hosts_text = """127.0.0.1 web-platform.test
|
||||
127.0.0.1 www.web-platform.test
|
||||
127.0.0.1 www1.web-platform.test
|
||||
127.0.0.1 www2.web-platform.test
|
||||
127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
|
||||
127.0.0.1 xn--lve-6lad.web-platform.test
|
||||
"""
|
||||
|
||||
def make_hosts_file():
|
||||
hosts_fd, hosts_path = tempfile.mkstemp()
|
||||
with os.fdopen(hosts_fd, "w") as f:
|
||||
f.write(hosts_text)
|
||||
return hosts_path
|
||||
|
||||
|
||||
class ServoTestharnessExecutor(ProcessTestExecutor):
|
||||
convert_result = testharness_result_converter
|
||||
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1, debug_info=None,
|
||||
pause_after_test=False):
|
||||
do_delayed_imports()
|
||||
ProcessTestExecutor.__init__(self, browser, server_config,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
self.pause_after_test = pause_after_test
|
||||
self.result_data = None
|
||||
self.result_flag = None
|
||||
self.protocol = Protocol(self, browser)
|
||||
self.hosts_path = make_hosts_file()
|
||||
|
||||
def teardown(self):
|
||||
try:
|
||||
os.unlink(self.hosts_path)
|
||||
except OSError:
|
||||
pass
|
||||
ProcessTestExecutor.teardown(self)
|
||||
|
||||
def do_test(self, test):
|
||||
self.result_data = None
|
||||
self.result_flag = threading.Event()
|
||||
|
||||
args = [render_arg(self.browser.render_backend), "--hard-fail", "-u", "Servo/wptrunner",
|
||||
"-Z", "replace-surrogates", "-z", self.test_url(test)]
|
||||
for stylesheet in self.browser.user_stylesheets:
|
||||
args += ["--user-stylesheet", stylesheet]
|
||||
for pref, value in test.environment.get('prefs', {}).iteritems():
|
||||
args += ["--pref", "%s=%s" % (pref, value)]
|
||||
args += self.browser.binary_args
|
||||
debug_args, command = browser_command(self.binary, args, self.debug_info)
|
||||
|
||||
self.command = command
|
||||
|
||||
if self.pause_after_test:
|
||||
self.command.remove("-z")
|
||||
|
||||
self.command = debug_args + self.command
|
||||
|
||||
env = os.environ.copy()
|
||||
env["HOST_FILE"] = self.hosts_path
|
||||
env["RUST_BACKTRACE"] = "1"
|
||||
|
||||
|
||||
if not self.interactive:
|
||||
self.proc = ProcessHandler(self.command,
|
||||
processOutputLine=[self.on_output],
|
||||
onFinish=self.on_finish,
|
||||
env=env,
|
||||
storeOutput=False)
|
||||
self.proc.run()
|
||||
else:
|
||||
self.proc = subprocess.Popen(self.command, env=env)
|
||||
|
||||
try:
|
||||
timeout = test.timeout * self.timeout_multiplier
|
||||
|
||||
# Now wait to get the output we expect, or until we reach the timeout
|
||||
if not self.interactive and not self.pause_after_test:
|
||||
wait_timeout = timeout + 5
|
||||
self.result_flag.wait(wait_timeout)
|
||||
else:
|
||||
wait_timeout = None
|
||||
self.proc.wait()
|
||||
|
||||
proc_is_running = True
|
||||
|
||||
if self.result_flag.is_set():
|
||||
if self.result_data is not None:
|
||||
result = self.convert_result(test, self.result_data)
|
||||
else:
|
||||
self.proc.wait()
|
||||
result = (test.result_cls("CRASH", None), [])
|
||||
proc_is_running = False
|
||||
else:
|
||||
result = (test.result_cls("TIMEOUT", None), [])
|
||||
|
||||
|
||||
if proc_is_running:
|
||||
if self.pause_after_test:
|
||||
self.logger.info("Pausing until the browser exits")
|
||||
self.proc.wait()
|
||||
else:
|
||||
self.proc.kill()
|
||||
except KeyboardInterrupt:
|
||||
self.proc.kill()
|
||||
raise
|
||||
|
||||
return result
|
||||
|
||||
def on_output(self, line):
|
||||
prefix = "ALERT: RESULT: "
|
||||
line = line.decode("utf8", "replace")
|
||||
if line.startswith(prefix):
|
||||
self.result_data = json.loads(line[len(prefix):])
|
||||
self.result_flag.set()
|
||||
else:
|
||||
if self.interactive:
|
||||
print line
|
||||
else:
|
||||
self.logger.process_output(self.proc.pid,
|
||||
line,
|
||||
" ".join(self.command))
|
||||
|
||||
def on_finish(self):
|
||||
self.result_flag.set()
|
||||
|
||||
|
||||
class TempFilename(object):
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
self.path = None
|
||||
|
||||
def __enter__(self):
|
||||
self.path = os.path.join(self.directory, str(uuid.uuid4()))
|
||||
return self.path
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
try:
|
||||
os.unlink(self.path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class ServoRefTestExecutor(ProcessTestExecutor):
|
||||
convert_result = reftest_result_converter
|
||||
|
||||
def __init__(self, browser, server_config, binary=None, timeout_multiplier=1,
|
||||
screenshot_cache=None, debug_info=None, pause_after_test=False):
|
||||
do_delayed_imports()
|
||||
ProcessTestExecutor.__init__(self,
|
||||
browser,
|
||||
server_config,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
|
||||
self.protocol = Protocol(self, browser)
|
||||
self.screenshot_cache = screenshot_cache
|
||||
self.implementation = RefTestImplementation(self)
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
self.hosts_path = make_hosts_file()
|
||||
|
||||
def teardown(self):
|
||||
try:
|
||||
os.unlink(self.hosts_path)
|
||||
except OSError:
|
||||
pass
|
||||
os.rmdir(self.tempdir)
|
||||
ProcessTestExecutor.teardown(self)
|
||||
|
||||
def screenshot(self, test, viewport_size, dpi):
|
||||
full_url = self.test_url(test)
|
||||
|
||||
with TempFilename(self.tempdir) as output_path:
|
||||
debug_args, command = browser_command(
|
||||
self.binary,
|
||||
[render_arg(self.browser.render_backend), "--hard-fail", "--exit",
|
||||
"-u", "Servo/wptrunner", "-Z", "disable-text-aa,load-webfonts-synchronously,replace-surrogates",
|
||||
"--output=%s" % output_path, full_url],
|
||||
self.debug_info)
|
||||
|
||||
for stylesheet in self.browser.user_stylesheets:
|
||||
command += ["--user-stylesheet", stylesheet]
|
||||
|
||||
for pref in test.environment.get('prefs', {}):
|
||||
command += ["--pref", pref]
|
||||
|
||||
if viewport_size:
|
||||
command += ["--resolution", viewport_size]
|
||||
|
||||
if dpi:
|
||||
command += ["--device-pixel-ratio", dpi]
|
||||
|
||||
self.command = debug_args + command
|
||||
|
||||
env = os.environ.copy()
|
||||
env["HOST_FILE"] = self.hosts_path
|
||||
env["RUST_BACKTRACE"] = "1"
|
||||
|
||||
if not self.interactive:
|
||||
self.proc = ProcessHandler(self.command,
|
||||
processOutputLine=[self.on_output],
|
||||
env=env)
|
||||
|
||||
|
||||
try:
|
||||
self.proc.run()
|
||||
timeout = test.timeout * self.timeout_multiplier + 5
|
||||
rv = self.proc.wait(timeout=timeout)
|
||||
except KeyboardInterrupt:
|
||||
self.proc.kill()
|
||||
raise
|
||||
else:
|
||||
self.proc = subprocess.Popen(self.command,
|
||||
env=env)
|
||||
try:
|
||||
rv = self.proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
self.proc.kill()
|
||||
raise
|
||||
|
||||
if rv is None:
|
||||
self.proc.kill()
|
||||
return False, ("EXTERNAL-TIMEOUT", None)
|
||||
|
||||
if rv != 0 or not os.path.exists(output_path):
|
||||
return False, ("CRASH", None)
|
||||
|
||||
with open(output_path) as f:
|
||||
# Might need to strip variable headers or something here
|
||||
data = f.read()
|
||||
return True, base64.b64encode(data)
|
||||
|
||||
def do_test(self, test):
|
||||
result = self.implementation.run_test(test)
|
||||
|
||||
return self.convert_result(test, result)
|
||||
|
||||
def on_output(self, line):
|
||||
line = line.decode("utf8", "replace")
|
||||
if self.interactive:
|
||||
print line
|
||||
else:
|
||||
self.logger.process_output(self.proc.pid,
|
||||
line,
|
||||
" ".join(self.command))
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from .base import (Protocol,
|
||||
RefTestExecutor,
|
||||
RefTestImplementation,
|
||||
TestharnessExecutor,
|
||||
strip_server)
|
||||
from .. import webdriver
|
||||
from ..testrunner import Stop
|
||||
|
||||
webdriver = None
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
extra_timeout = 5
|
||||
|
||||
|
||||
def do_delayed_imports():
|
||||
global webdriver
|
||||
import webdriver
|
||||
|
||||
|
||||
class ServoWebDriverProtocol(Protocol):
|
||||
def __init__(self, executor, browser, capabilities, **kwargs):
|
||||
do_delayed_imports()
|
||||
Protocol.__init__(self, executor, browser)
|
||||
self.capabilities = capabilities
|
||||
self.host = browser.webdriver_host
|
||||
self.port = browser.webdriver_port
|
||||
self.session = None
|
||||
|
||||
def setup(self, runner):
|
||||
"""Connect to browser via WebDriver."""
|
||||
self.runner = runner
|
||||
|
||||
url = "http://%s:%d" % (self.host, self.port)
|
||||
session_started = False
|
||||
try:
|
||||
self.session = webdriver.Session(self.host, self.port,
|
||||
extension=webdriver.servo.ServoCommandExtensions)
|
||||
self.session.start()
|
||||
except:
|
||||
self.logger.warning(
|
||||
"Connecting with WebDriver failed:\n%s" % traceback.format_exc())
|
||||
else:
|
||||
self.logger.debug("session started")
|
||||
session_started = True
|
||||
|
||||
if not session_started:
|
||||
self.logger.warning("Failed to connect via WebDriver")
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
self.executor.runner.send_message("init_succeeded")
|
||||
|
||||
def teardown(self):
|
||||
self.logger.debug("Hanging up on WebDriver session")
|
||||
try:
|
||||
self.session.end()
|
||||
except:
|
||||
pass
|
||||
|
||||
def is_alive(self):
|
||||
try:
|
||||
# Get a simple property over the connection
|
||||
self.session.window_handle
|
||||
# TODO what exception?
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def after_connect(self):
|
||||
pass
|
||||
|
||||
def wait(self):
|
||||
while True:
|
||||
try:
|
||||
self.session.execute_async_script("")
|
||||
except webdriver.TimeoutException:
|
||||
pass
|
||||
except (socket.timeout, IOError):
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(traceback.format_exc(e))
|
||||
break
|
||||
|
||||
def on_environment_change(self, old_environment, new_environment):
|
||||
#Unset all the old prefs
|
||||
self.session.extension.reset_prefs(*old_environment.get("prefs", {}).keys())
|
||||
self.session.extension.set_prefs(new_environment.get("prefs", {}))
|
||||
|
||||
|
||||
class ServoWebDriverRun(object):
|
||||
def __init__(self, func, session, url, timeout, current_timeout=None):
|
||||
self.func = func
|
||||
self.result = None
|
||||
self.session = session
|
||||
self.url = url
|
||||
self.timeout = timeout
|
||||
self.result_flag = threading.Event()
|
||||
|
||||
def run(self):
|
||||
executor = threading.Thread(target=self._run)
|
||||
executor.start()
|
||||
|
||||
flag = self.result_flag.wait(self.timeout + extra_timeout)
|
||||
if self.result is None:
|
||||
assert not flag
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
|
||||
return self.result
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
self.result = True, self.func(self.session, self.url, self.timeout)
|
||||
except webdriver.TimeoutException:
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
except (socket.timeout, IOError):
|
||||
self.result = False, ("CRASH", None)
|
||||
except Exception as e:
|
||||
message = getattr(e, "message", "")
|
||||
if message:
|
||||
message += "\n"
|
||||
message += traceback.format_exc(e)
|
||||
self.result = False, ("ERROR", e)
|
||||
finally:
|
||||
self.result_flag.set()
|
||||
|
||||
|
||||
def timeout_func(timeout):
|
||||
if timeout:
|
||||
t0 = time.time()
|
||||
return lambda: time.time() - t0 > timeout + extra_timeout
|
||||
else:
|
||||
return lambda: False
|
||||
|
||||
|
||||
class ServoWebDriverTestharnessExecutor(TestharnessExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
close_after_done=True, capabilities=None, debug_info=None):
|
||||
TestharnessExecutor.__init__(self, browser, server_config, timeout_multiplier=1,
|
||||
debug_info=None)
|
||||
self.protocol = ServoWebDriverProtocol(self, browser, capabilities=capabilities)
|
||||
with open(os.path.join(here, "testharness_servodriver.js")) as f:
|
||||
self.script = f.read()
|
||||
self.timeout = None
|
||||
|
||||
def on_protocol_change(self, new_protocol):
|
||||
pass
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive()
|
||||
|
||||
def do_test(self, test):
|
||||
url = self.test_url(test)
|
||||
|
||||
timeout = test.timeout * self.timeout_multiplier + extra_timeout
|
||||
|
||||
if timeout != self.timeout:
|
||||
try:
|
||||
self.protocol.session.timeouts.script = timeout
|
||||
self.timeout = timeout
|
||||
except IOError:
|
||||
self.logger.error("Lost webdriver connection")
|
||||
return Stop
|
||||
|
||||
success, data = ServoWebDriverRun(self.do_testharness,
|
||||
self.protocol.session,
|
||||
url,
|
||||
timeout).run()
|
||||
|
||||
if success:
|
||||
return self.convert_result(test, data)
|
||||
|
||||
return (test.result_cls(*data), [])
|
||||
|
||||
def do_testharness(self, session, url, timeout):
|
||||
session.url = url
|
||||
result = json.loads(
|
||||
session.execute_async_script(
|
||||
self.script % {"abs_url": url,
|
||||
"url": strip_server(url),
|
||||
"timeout_multiplier": self.timeout_multiplier,
|
||||
"timeout": timeout * 1000}))
|
||||
# Prevent leaking every page in history until Servo develops a more sane
|
||||
# page cache
|
||||
session.back()
|
||||
return result
|
||||
|
||||
|
||||
class TimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ServoWebDriverRefTestExecutor(RefTestExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
screenshot_cache=None, capabilities=None, debug_info=None):
|
||||
"""Selenium WebDriver-based executor for reftests"""
|
||||
RefTestExecutor.__init__(self,
|
||||
browser,
|
||||
server_config,
|
||||
screenshot_cache=screenshot_cache,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
self.protocol = ServoWebDriverProtocol(self, browser,
|
||||
capabilities=capabilities)
|
||||
self.implementation = RefTestImplementation(self)
|
||||
self.timeout = None
|
||||
with open(os.path.join(here, "reftest-wait_servodriver.js")) as f:
|
||||
self.wait_script = f.read()
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive()
|
||||
|
||||
def do_test(self, test):
|
||||
try:
|
||||
result = self.implementation.run_test(test)
|
||||
return self.convert_result(test, result)
|
||||
except IOError:
|
||||
return test.result_cls("CRASH", None), []
|
||||
except TimeoutError:
|
||||
return test.result_cls("TIMEOUT", None), []
|
||||
except Exception as e:
|
||||
message = getattr(e, "message", "")
|
||||
if message:
|
||||
message += "\n"
|
||||
message += traceback.format_exc(e)
|
||||
return test.result_cls("ERROR", message), []
|
||||
|
||||
def screenshot(self, test, viewport_size, dpi):
|
||||
# https://github.com/w3c/wptrunner/issues/166
|
||||
assert viewport_size is None
|
||||
assert dpi is None
|
||||
|
||||
timeout = (test.timeout * self.timeout_multiplier + extra_timeout
|
||||
if self.debug_info is None else None)
|
||||
|
||||
if self.timeout != timeout:
|
||||
try:
|
||||
self.protocol.session.timeouts.script = timeout
|
||||
self.timeout = timeout
|
||||
except IOError:
|
||||
self.logger.error("Lost webdriver connection")
|
||||
return Stop
|
||||
|
||||
return ServoWebDriverRun(self._screenshot,
|
||||
self.protocol.session,
|
||||
self.test_url(test),
|
||||
timeout).run()
|
||||
|
||||
def _screenshot(self, session, url, timeout):
|
||||
session.url = url
|
||||
session.execute_async_script(self.wait_script)
|
||||
return session.screenshot()
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from .base import TestExecutor
|
||||
|
||||
|
||||
class ProcessTestExecutor(TestExecutor):
|
||||
def __init__(self, *args, **kwargs):
|
||||
TestExecutor.__init__(self, *args, **kwargs)
|
||||
self.binary = self.browser.binary
|
||||
self.interactive = (False if self.debug_info is None
|
||||
else self.debug_info.interactive)
|
||||
|
||||
def setup(self, runner):
|
||||
self.runner = runner
|
||||
self.runner.send_message("init_succeeded")
|
||||
return True
|
||||
|
||||
def is_alive(self):
|
||||
return True
|
||||
|
||||
def do_test(self, test):
|
||||
raise NotImplementedError
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from . import fixtures
|
||||
from .runner import run
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue